From 7d1918b385c600e1910dd5b744b583a42a26d8a1 Mon Sep 17 00:00:00 2001 From: ed Date: Wed, 13 Oct 2021 14:26:32 +0100 Subject: [PATCH] macOS/iOS: Replace compile-time deployment target checks with runtime checks using the available keyword --- .../native/juce_ios_Audio.cpp | 17 +- .../native/juce_mac_CoreMidi.mm | 4 +- .../AU/juce_AUv3_Wrapper.mm | 57 +- .../juce_AudioUnitPluginFormat.mm | 57 +- ..._mac_BluetoothMidiDevicePairingDialogue.mm | 25 +- .../native/juce_BasicNativeHeaders.h | 2 +- modules/juce_core/native/juce_mac_Files.mm | 42 +- modules/juce_core/native/juce_mac_Network.mm | 835 +++++++++--------- .../juce_core/native/juce_mac_SystemStats.mm | 24 +- .../accessibility/juce_ios_Accessibility.mm | 6 +- .../native/juce_ios_FileChooser.mm | 4 +- .../native/juce_ios_UIViewComponentPeer.mm | 19 +- .../native/juce_ios_Windowing.mm | 5 +- .../native/juce_mac_NSViewComponentPeer.mm | 19 +- .../native/juce_mac_AppleRemote.mm | 15 +- .../native/juce_mac_SystemTrayIcon.cpp | 2 +- .../native/juce_mac_WebBrowserComponent.mm | 577 ++++++------ modules/juce_opengl/native/juce_OpenGL_osx.h | 22 +- .../juce_video/capture/juce_CameraDevice.cpp | 2 +- .../juce_video/native/juce_ios_CameraDevice.h | 36 +- .../juce_video/native/juce_mac_CameraDevice.h | 399 +++++---- 21 files changed, 1070 insertions(+), 1099 deletions(-) diff --git a/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/modules/juce_audio_devices/native/juce_ios_Audio.cpp index 4fb2a9e774..e8f190362a 100644 --- a/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ b/modules/juce_audio_devices/native/juce_ios_Audio.cpp @@ -722,11 +722,20 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead, &dataSize); if (err == noErr) { - #if (! defined __IPHONE_10_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0) - [[UIApplication sharedApplication] openURL: (NSURL*)hostUrl]; - #else - [[UIApplication sharedApplication] openURL: (NSURL*)hostUrl options: @{} completionHandler: nil]; + #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + if (@available (iOS 10.0, *)) + { + [[UIApplication sharedApplication] openURL: (NSURL*) hostUrl + options: @{} + completionHandler: nil]; + + return; + } #endif + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + [[UIApplication sharedApplication] openURL: (NSURL*) hostUrl]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } } diff --git a/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm b/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm index ba2b9611bd..4e082e0924 100644 --- a/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm +++ b/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm @@ -82,7 +82,7 @@ namespace CoreMidiHelpers struct Sender; #if JUCE_HAS_NEW_COREMIDI_API - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new") + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new") template <> struct Sender : public SenderBase @@ -829,7 +829,7 @@ namespace CoreMidiHelpers struct CreatorFunctions; #if JUCE_HAS_NEW_COREMIDI_API - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new") + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new") template <> struct CreatorFunctions diff --git a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm index d67b3ca9e6..344dac4299 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -28,27 +28,19 @@ #if JucePlugin_Build_AUv3 -#if JUCE_MAC - #if (! defined MAC_OS_X_VERSION_MIN_REQUIRED) || (! defined MAC_OS_X_VERSION_10_11) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11) - #error AUv3 needs Deployment Target OS X 10.11 or higher to compile - #endif - #if (defined MAC_OS_X_VERSION_10_13) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13) - #define JUCE_AUV3_MIDI_OUTPUT_SUPPORTED 1 - #define JUCE_AUV3_VIEW_CONFIG_SUPPORTED 1 - #endif - #if defined (MAC_OS_VERSION_12_0) - #define JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED 1 - #endif +#if JUCE_MAC && ! (defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + #error AUv3 needs Deployment Target OS X 10.11 or higher to compile #endif -#if JUCE_IOS - #if (defined __IPHONE_11_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0) - #define JUCE_AUV3_MIDI_OUTPUT_SUPPORTED 1 - #define JUCE_AUV3_VIEW_CONFIG_SUPPORTED 1 - #endif - #if (defined __IPHONE_15_0) - #define JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED 1 - #endif +#if (JUCE_IOS && defined (__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0) \ + || (JUCE_MAC && defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0) + #define JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED 1 +#endif + +#if (JUCE_IOS && defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) \ + || (JUCE_MAC && defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13) + #define JUCE_AUV3_MIDI_OUTPUT_SUPPORTED 1 + #define JUCE_AUV3_VIEW_CONFIG_SUPPORTED 1 #endif #ifndef __OBJC2__ @@ -210,8 +202,10 @@ public: //============================================================================== #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new") virtual NSIndexSet* getSupportedViewConfigurations (NSArray*) = 0; - virtual void selectViewConfiguration (AUAudioUnitViewConfiguration*) = 0; + virtual void selectViewConfiguration (AUAudioUnitViewConfiguration*) = 0; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE #endif private: @@ -265,7 +259,8 @@ private: JUCE_END_IGNORE_WARNINGS_GCC_LIKE #if JUCE_AUV3_MIDI_OUTPUT_SUPPORTED - addMethod (@selector (MIDIOutputNames), getMIDIOutputNames, "@@:"); + if (@available (macOS 10.13, iOS 11.0, *)) + addMethod (@selector (MIDIOutputNames), getMIDIOutputNames, "@@:"); #endif //============================================================================== @@ -284,8 +279,11 @@ private: //============================================================================== #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED - addMethod (@selector (supportedViewConfigurations:), getSupportedViewConfigurations, "@@:@"); - addMethod (@selector (selectViewConfiguration:), selectViewConfiguration, "v@:@"); + if (@available (macOS 10.13, iOS 11.0, *)) + { + addMethod (@selector (supportedViewConfigurations:), getSupportedViewConfigurations, "@@:@"); + addMethod (@selector (selectViewConfiguration:), selectViewConfiguration, "v@:@"); + } #endif registerClass(); @@ -396,8 +394,10 @@ private: //============================================================================== #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new") static NSIndexSet* getSupportedViewConfigurations (id self, SEL, NSArray* configs) { return _this (self)->getSupportedViewConfigurations (configs); } - static void selectViewConfiguration (id self, SEL, AUAudioUnitViewConfiguration* config) { _this (self)->selectViewConfiguration (config); } + static void selectViewConfiguration (id self, SEL, AUAudioUnitViewConfiguration* config) { _this (self)->selectViewConfiguration (config); } + JUCE_END_IGNORE_WARNINGS_GCC_LIKE #endif }; @@ -896,6 +896,7 @@ public: //============================================================================== #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new") NSIndexSet* getSupportedViewConfigurations (NSArray* configs) override { auto supportedViewIndecies = [[NSMutableIndexSet alloc] init]; @@ -932,6 +933,7 @@ public: { processorHolder->viewConfiguration.reset (new AudioProcessorHolder::ViewConfig { [config width], [config height], [config hostHasController] == YES }); } + JUCE_END_IGNORE_WARNINGS_GCC_LIKE #endif struct ScopedKeyChange @@ -1598,10 +1600,11 @@ private: // send MIDI #if JucePlugin_ProducesMidiOutput && JUCE_AUV3_MIDI_OUTPUT_SUPPORTED - if (auto midiOut = [au MIDIOutputEventBlock]) + if (@available (macOS 10.13, iOS 11.0, *)) { - for (const auto metadata : midiMessages) - midiOut (metadata.samplePosition, 0, metadata.numBytes, metadata.data); + if (auto midiOut = [au MIDIOutputEventBlock]) + for (const auto metadata : midiMessages) + midiOut (metadata.samplePosition, 0, metadata.numBytes, metadata.data); } #endif diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index 1327c4eb3e..3e502c38b7 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -39,18 +39,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") #include #endif -#ifndef JUCE_SUPPORTS_AUv3 - #if __OBJC2__ \ - && (JUCE_IOS || (defined (MAC_OS_X_VERSION_10_11) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11))) - #define JUCE_SUPPORTS_AUv3 1 - #else - #define JUCE_SUPPORTS_AUv3 0 - #endif -#endif - -#if JUCE_SUPPORTS_AUv3 - #include -#endif +#include #include #include @@ -547,9 +536,7 @@ public: AudioComponentGetDescription (auComponent, &componentDesc); - #if JUCE_SUPPORTS_AUv3 isAUv3 = ((componentDesc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); - #endif wantsMidiMessages = componentDesc.componentType == kAudioUnitType_MusicDevice || componentDesc.componentType == kAudioUnitType_MusicEffect @@ -1136,18 +1123,12 @@ public: bool hasEditor() const override { - #if JUCE_MAC - return true; - #elif JUCE_SUPPORTS_AUv3 UInt32 dataSize; Boolean isWritable; return (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr && dataSize == sizeof (uintptr_t) && isWritable != 0); - #else - return false; - #endif } AudioProcessorEditor* createEditor() override; @@ -2261,10 +2242,8 @@ public: { addAndMakeVisible (wrapper); - #if JUCE_SUPPORTS_AUv3 viewControllerCallback = CreateObjCBlock (this, &AudioUnitPluginWindowCocoa::requestViewControllerCallback); - #endif setOpaque (true); setVisible (true); @@ -2284,7 +2263,6 @@ public: } } - #if JUCE_SUPPORTS_AUv3 void embedViewController (JUCE_IOS_MAC_VIEW* pluginView, const CGSize& size) { wrapper.setView (pluginView); @@ -2299,7 +2277,6 @@ public: wrapper.setSize (static_cast (size.width), static_cast (size.height)); #endif } - #endif bool isValid() const { return wrapper.getView() != nil || waitingForViewCallback; } @@ -2323,10 +2300,8 @@ private: AudioUnitPluginInstance& plugin; AudioUnitFormatHelpers::AutoResizingNSViewComponent wrapper; - #if JUCE_SUPPORTS_AUv3 typedef void (^ViewControllerCallbackBlock)(AUViewControllerBase *); ObjCBlock viewControllerCallback; - #endif bool waitingForViewCallback = false; @@ -2375,7 +2350,6 @@ private: dataSize = 0; isWritable = false; - #if JUCE_SUPPORTS_AUv3 if (AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr && dataSize == sizeof (ViewControllerCallbackBlock)) @@ -2391,7 +2365,6 @@ private: waitingForViewCallback = false; } - #endif #if JUCE_MAC if (createGenericViewIfNeeded && (pluginView == nil)) @@ -2418,7 +2391,6 @@ private: return pluginView != nil; } - #if JUCE_SUPPORTS_AUv3 void requestViewControllerCallback (AUViewControllerBase* controller) { const auto viewSize = [&controller] @@ -2459,7 +2431,6 @@ private: embedViewController ([controller view], viewSize); } } - #endif }; #if JUCE_SUPPORT_CARBON @@ -2693,23 +2664,17 @@ void AudioUnitPluginFormat::createPluginInstance (const PluginDescription& desc, struct AUAsyncInitializationCallback { - #if JUCE_SUPPORTS_AUv3 typedef void (^AUCompletionCallbackBlock)(AudioComponentInstance, OSStatus); - #endif AUAsyncInitializationCallback (double inSampleRate, int inFramesPerBuffer, PluginCreationCallback inOriginalCallback) : sampleRate (inSampleRate), framesPerBuffer (inFramesPerBuffer), originalCallback (std::move (inOriginalCallback)) { - #if JUCE_SUPPORTS_AUv3 block = CreateObjCBlock (this, &AUAsyncInitializationCallback::completion); - #endif } - #if JUCE_SUPPORTS_AUv3 - AUCompletionCallbackBlock getBlock() noexcept { return block; } - #endif + AUCompletionCallbackBlock getBlock() noexcept { return block; } void completion (AudioComponentInstance audioUnit, OSStatus err) { @@ -2734,26 +2699,18 @@ void AudioUnitPluginFormat::createPluginInstance (const PluginDescription& desc, double sampleRate; int framesPerBuffer; PluginCreationCallback originalCallback; - - #if JUCE_SUPPORTS_AUv3 ObjCBlock block; - #endif }; auto callbackBlock = new AUAsyncInitializationCallback (rate, blockSize, std::move (callback)); - #if JUCE_SUPPORTS_AUv3 - //============================================================================== - bool isAUv3 = ((componentDesc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); - - if (isAUv3) + if ((componentDesc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0) { AudioComponentInstantiate (auComponent, kAudioComponentInstantiation_LoadOutOfProcess, callbackBlock->getBlock()); return; } - #endif // JUCE_SUPPORTS_AUv3 AudioComponentInstance audioUnit; auto err = AudioComponentInstanceNew(auComponent, &audioUnit); @@ -2767,7 +2724,6 @@ void AudioUnitPluginFormat::createPluginInstance (const PluginDescription& desc, bool AudioUnitPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription& desc) const { - #if JUCE_SUPPORTS_AUv3 String pluginName, version, manufacturer; AudioComponentDescription componentDesc; @@ -2780,9 +2736,6 @@ bool AudioUnitPluginFormat::requiresUnblockedMessageThreadDuringCreation (const if (AudioComponentGetDescription (auComp, &componentDesc) == noErr) return ((componentDesc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); } - #else - ignoreUnused (desc); - #endif return false; } @@ -2815,11 +2768,9 @@ StringArray AudioUnitPluginFormat::searchPathsForPlugins (const FileSearchPath&, { ignoreUnused (allowPluginsWhichRequireAsynchronousInstantiation); - #if JUCE_SUPPORTS_AUv3 - bool isAUv3 = ((desc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); + const auto isAUv3 = ((desc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); if (allowPluginsWhichRequireAsynchronousInstantiation || ! isAUv3) - #endif result.add (AudioUnitFormatHelpers::createPluginIdentifier (desc)); } } diff --git a/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm b/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm index f558db0821..7b60933a35 100644 --- a/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm +++ b/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm @@ -26,8 +26,6 @@ namespace juce { -#if defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 - //============================================================================== class BluetoothMidiPairingWindowClass : public ObjCClass { @@ -159,20 +157,12 @@ private: bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback, Rectangle* bounds) { - new BluetoothMidiSelectorWindowHelper (exitCallback, bounds); - return true; -} - -bool BluetoothMidiDevicePairingDialogue::isAvailable() -{ - return true; -} - -#else + if (@available (macOS 10.11, *)) + { + new BluetoothMidiSelectorWindowHelper (exitCallback, bounds); + return true; + } -bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback, - Rectangle*) -{ std::unique_ptr cb (exitCallback); // This functionality is unavailable when targetting OSX < 10.11. Instead, // you should pair Bluetooth MIDI devices using the "Audio MIDI Setup" app @@ -183,9 +173,10 @@ bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* bool BluetoothMidiDevicePairingDialogue::isAvailable() { + if (@available (macOS 10.11, *)) + return true; + return false; } -#endif - } // namespace juce diff --git a/modules/juce_core/native/juce_BasicNativeHeaders.h b/modules/juce_core/native/juce_BasicNativeHeaders.h index 06d81d5141..65c3f83389 100644 --- a/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -28,7 +28,7 @@ #if JUCE_MAC || JUCE_IOS #if JUCE_IOS - #if JUCE_MODULE_AVAILABLE_juce_opengl && defined (__IPHONE_12_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_12_0 + #if JUCE_MODULE_AVAILABLE_juce_opengl && defined (__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0 #define GLES_SILENCE_DEPRECATION 1 #endif diff --git a/modules/juce_core/native/juce_mac_Files.mm b/modules/juce_core/native/juce_mac_Files.mm index cf7a269edb..7b6d6daaee 100644 --- a/modules/juce_core/native/juce_mac_Files.mm +++ b/modules/juce_core/native/juce_mac_Files.mm @@ -410,12 +410,20 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& #if JUCE_IOS ignoreUnused (parameters); - #if (! defined __IPHONE_OS_VERSION_MIN_REQUIRED) || (! defined __IPHONE_10_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0) - return [[UIApplication sharedApplication] openURL: filenameAsURL]; - #else - [[UIApplication sharedApplication] openURL: filenameAsURL options: @{} completionHandler: nil]; - return true; + #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + if (@available (iOS 10.0, *)) + { + [[UIApplication sharedApplication] openURL: filenameAsURL + options: @{} + completionHandler: nil]; + + return true; + } #endif + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + return [[UIApplication sharedApplication] openURL: filenameAsURL]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE #else NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; @@ -434,16 +442,21 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& for (int i = 0; i < params.size(); ++i) [paramArray addObject: juceStringToNS (params[i])]; - #if (defined MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15 - auto config = [NSWorkspaceOpenConfiguration configuration]; - [config setCreatesNewApplicationInstance: YES]; - config.arguments = paramArray; + #if defined (MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15 + if (@available (macOS 10.15, *)) + { + auto config = [NSWorkspaceOpenConfiguration configuration]; + [config setCreatesNewApplicationInstance: YES]; + config.arguments = paramArray; + + [workspace openApplicationAtURL: filenameAsURL + configuration: config + completionHandler: nil]; + + return true; + } + #endif - [workspace openApplicationAtURL: filenameAsURL - configuration: config - completionHandler: nil]; - return true; - #else NSMutableDictionary* dict = [[NSMutableDictionary new] autorelease]; [dict setObject: paramArray @@ -453,7 +466,6 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance configuration: dict error: nil]; - #endif } if (file.exists()) diff --git a/modules/juce_core/native/juce_mac_Network.mm b/modules/juce_core/native/juce_mac_Network.mm index 6c7de63cc9..22b96a8524 100644 --- a/modules/juce_core/native/juce_mac_Network.mm +++ b/modules/juce_core/native/juce_mac_Network.mm @@ -109,18 +109,295 @@ bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& targetEmailA } //============================================================================== -// Unfortunately, we need to have this ugly ifdef here as long as some older OS X versions do not support NSURLSession -#if JUCE_IOS || (defined (MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) - -//============================================================================== -class URLConnectionState : private Thread +class URLConnectionStateBase : public Thread { public: - URLConnectionState (NSURLRequest* req, const int maxRedirects) + explicit URLConnectionStateBase (NSURLRequest* req, int maxRedirects) : Thread ("http connection"), request ([req retain]), data ([[NSMutableData data] retain]), numRedirectsToFollow (maxRedirects) + { + } + + virtual ~URLConnectionStateBase() = default; + + virtual void cancel() = 0; + virtual bool start (WebInputStream&, WebInputStream::Listener*) = 0; + virtual int read (char* dest, int numBytes) = 0; + + int64 getContentLength() const noexcept { return contentLength; } + NSDictionary* getHeaders() const noexcept { return headers; } + int getStatusCode() const noexcept { return statusCode; } + NSInteger getErrorCode() const noexcept { return nsUrlErrorCode; } + +protected: + CriticalSection dataLock, createConnectionLock; + id delegate = nil; + NSDictionary* headers = nil; + NSURLRequest* request = nil; + NSMutableData* data = nil; + int64 contentLength = -1; + int statusCode = 0; + NSInteger nsUrlErrorCode = 0; + + std::atomic initialised { false }, hasFailed { false }, hasFinished { false }; + const int numRedirectsToFollow; + int numRedirects = 0; + int64 latestTotalBytes = 0; + bool hasBeenCancelled = false; + +private: + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionStateBase) +}; + +#if JUCE_MAC +// This version is only used for backwards-compatibility with older OSX targets, +// so we'll turn off deprecation warnings. This code will be removed at some point +// in the future. +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated") +class URLConnectionStatePreYosemite : public URLConnectionStateBase +{ +public: + URLConnectionStatePreYosemite (NSURLRequest* req, const int maxRedirects) + : URLConnectionStateBase (req, maxRedirects) + { + static DelegateClass cls; + delegate = [cls.createInstance() init]; + DelegateClass::setState (delegate, this); + } + + ~URLConnectionStatePreYosemite() override + { + stop(); + + [connection release]; + [request release]; + [headers release]; + [delegate release]; + [data release]; + } + + bool start (WebInputStream& inputStream, WebInputStream::Listener* listener) override + { + startThread(); + + while (isThreadRunning() && ! initialised) + { + if (listener != nullptr) + if (! listener->postDataSendProgress (inputStream, (int) latestTotalBytes, (int) [[request HTTPBody] length])) + return false; + + Thread::sleep (1); + } + + return connection != nil && ! hasFailed; + } + + void stop() + { + { + const ScopedLock dLock (dataLock); + const ScopedLock connectionLock (createConnectionLock); + + hasBeenCancelled = true; + + if (connection != nil) + [connection cancel]; + } + + stopThread (10000); + } + + void cancel() override + { + hasFinished = hasFailed = true; + stop(); + } + + int read (char* dest, int numBytes) override + { + int numDone = 0; + + while (numBytes > 0) + { + const ScopedLock sl (dataLock); + auto available = jmin (numBytes, (int) [data length]); + + if (available > 0) + { + [data getBytes: dest length: (NSUInteger) available]; + [data replaceBytesInRange: NSMakeRange (0, (NSUInteger) available) withBytes: nil length: 0]; + + numDone += available; + numBytes -= available; + dest += available; + } + else + { + if (hasFailed || hasFinished) + break; + + const ScopedUnlock sul (dataLock); + Thread::sleep (1); + } + } + + return numDone; + } + + void didReceiveResponse (NSURLResponse* response) + { + { + const ScopedLock sl (dataLock); + [data setLength: 0]; + } + + contentLength = [response expectedContentLength]; + + [headers release]; + headers = nil; + + if ([response isKindOfClass: [NSHTTPURLResponse class]]) + { + NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response; + headers = [[httpResponse allHeaderFields] retain]; + statusCode = (int) [httpResponse statusCode]; + } + + initialised = true; + } + + NSURLRequest* willSendRequest (NSURLRequest* newRequest, NSURLResponse* redirectResponse) + { + if (redirectResponse != nullptr) + { + if (numRedirects >= numRedirectsToFollow) + return nil; // Cancel redirect and allow connection to continue + + ++numRedirects; + } + + return newRequest; + } + + void didFailWithError (NSError* error) + { + DBG (nsStringToJuce ([error description])); ignoreUnused (error); + nsUrlErrorCode = [error code]; + hasFailed = true; + initialised = true; + signalThreadShouldExit(); + } + + void didReceiveData (NSData* newData) + { + const ScopedLock sl (dataLock); + [data appendData: newData]; + initialised = true; + } + + void didSendBodyData (NSInteger totalBytesWritten, NSInteger /*totalBytesExpected*/) + { + latestTotalBytes = static_cast (totalBytesWritten); + } + + void finishedLoading() + { + hasFinished = true; + initialised = true; + signalThreadShouldExit(); + } + + void run() override + { + { + const ScopedLock lock (createConnectionLock); + + if (hasBeenCancelled) + return; + + connection = [[NSURLConnection alloc] initWithRequest: request + delegate: delegate]; + } + + while (! threadShouldExit()) + { + JUCE_AUTORELEASEPOOL + { + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; + } + } + } + +private: + //============================================================================== + struct DelegateClass : public ObjCClass + { + DelegateClass() : ObjCClass ("JUCENetworkDelegate_") + { + addIvar ("state"); + + addMethod (@selector (connection:didReceiveResponse:), didReceiveResponse, "v@:@@"); + addMethod (@selector (connection:didFailWithError:), didFailWithError, "v@:@@"); + addMethod (@selector (connection:didReceiveData:), didReceiveData, "v@:@@"); + addMethod (@selector (connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:), + connectionDidSendBodyData, "v@:@iii"); + addMethod (@selector (connectionDidFinishLoading:), connectionDidFinishLoading, "v@:@"); + addMethod (@selector (connection:willSendRequest:redirectResponse:), willSendRequest, "@@:@@@"); + + registerClass(); + } + + static void setState (id self, URLConnectionStatePreYosemite* state) { object_setInstanceVariable (self, "state", state); } + static URLConnectionStatePreYosemite* getState (id self) { return getIvar (self, "state"); } + + private: + static void didReceiveResponse (id self, SEL, NSURLConnection*, NSURLResponse* response) + { + getState (self)->didReceiveResponse (response); + } + + static void didFailWithError (id self, SEL, NSURLConnection*, NSError* error) + { + getState (self)->didFailWithError (error); + } + + static void didReceiveData (id self, SEL, NSURLConnection*, NSData* newData) + { + getState (self)->didReceiveData (newData); + } + + static NSURLRequest* willSendRequest (id self, SEL, NSURLConnection*, NSURLRequest* request, NSURLResponse* response) + { + return getState (self)->willSendRequest (request, response); + } + + static void connectionDidSendBodyData (id self, SEL, NSURLConnection*, NSInteger, NSInteger totalBytesWritten, NSInteger totalBytesExpected) + { + getState (self)->didSendBodyData (totalBytesWritten, totalBytesExpected); + } + + static void connectionDidFinishLoading (id self, SEL, NSURLConnection*) + { + getState (self)->finishedLoading(); + } + }; + + NSURLConnection* connection = nil; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionStatePreYosemite) +}; +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif + +//============================================================================== +class URLConnectionState : public URLConnectionStateBase +{ +public: + URLConnectionState (NSURLRequest* req, const int maxRedirects) + : URLConnectionStateBase (req, maxRedirects) { static DelegateClass cls; delegate = [cls.createInstance() init]; @@ -151,11 +428,10 @@ public: [data release]; } - void cancel() + void cancel() override { { - const ScopedLock lock (createTaskLock); - + const ScopedLock lock (createConnectionLock); hasBeenCancelled = true; } @@ -163,10 +439,10 @@ public: stopThread (10000); } - bool start (WebInputStream& inputStream, WebInputStream::Listener* listener) + bool start (WebInputStream& inputStream, WebInputStream::Listener* listener) override { { - const ScopedLock lock (createTaskLock); + const ScopedLock lock (createConnectionLock); if (hasBeenCancelled) return false; @@ -186,7 +462,7 @@ public: return true; } - int read (char* dest, int numBytes) + int read (char* dest, int numBytes) override { int numDone = 0; @@ -304,7 +580,7 @@ public: delegateQueue: [NSOperationQueue currentQueue]] retain]; { - const ScopedLock lock (createTaskLock); + const ScopedLock lock (createConnectionLock); if (! hasBeenCancelled) task = [session dataTaskWithRequest: request]; @@ -323,23 +599,6 @@ public: initialised = true; } - int64 contentLength = -1; - CriticalSection dataLock; - id delegate = nil; - NSURLRequest* request = nil; - NSURLSession* session = nil; - NSURLSessionTask* task = nil; - NSMutableData* data = nil; - NSDictionary* headers = nil; - int statusCode = 0; - std::atomic initialised { false }, hasFailed { false }, hasFinished { false }; - bool isBeingDeleted = false; - const int numRedirectsToFollow; - int numRedirects = 0; - int64 latestTotalBytes = 0; - CriticalSection createTaskLock; - bool hasBeenCancelled = false; - private: //============================================================================== struct DelegateClass : public ObjCClass @@ -403,6 +662,10 @@ private: } }; + NSURLSession* session = nil; + NSURLSessionTask* task = nil; + bool isBeingDeleted = false; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState) }; @@ -496,451 +759,181 @@ struct BackgroundDownloadTask : public URL::DownloadTask //============================================================================== URL::DownloadTask::Listener* listener; NSObject* delegate = nil; - NSURLSession* session = nil; - NSURLSessionDownloadTask* downloadTask = nil; - bool connectFinished = false, hasBeenDestroyed = false; - Atomic calledComplete; - WaitableEvent connectionEvent, destroyEvent; - String uniqueIdentifier; - - static HashMap activeSessions; - - void didWriteData (int64 totalBytesWritten, int64 totalBytesExpectedToWrite) - { - downloaded = totalBytesWritten; - - if (contentLength == -1) - contentLength = totalBytesExpectedToWrite; - - if (connectFinished && error == false && finished == false && listener != nullptr) - listener->progress (this, totalBytesWritten, contentLength); - - connectionEvent.signal(); - } - - void didFinishDownloadingToURL (NSURL* location) - { - auto* fileManager = [NSFileManager defaultManager]; - error = ([fileManager moveItemAtURL: location - toURL: createNSURLFromFile (targetLocation) - error: nil] == NO); - httpCode = 200; - finished = true; - - connectionEvent.signal(); - - if (listener != nullptr && calledComplete.exchange (1) == 0) - { - if (contentLength > 0 && downloaded < contentLength) - { - downloaded = contentLength; - listener->progress (this, downloaded, contentLength); - } - - listener->finished (this, !error); - } - } - - static int getHTTPErrorCode (NSError* nsError) - { - // see https://developer.apple.com/reference/foundation/nsurlsessiondownloadtask?language=objc - switch ([nsError code]) - { - case NSURLErrorUserAuthenticationRequired: return 401; - case NSURLErrorNoPermissionsToReadFile: return 403; - case NSURLErrorFileDoesNotExist: return 404; - default: return 500; - } - } - - void didCompleteWithError (NSError* nsError) - { - if (calledComplete.exchange (1) == 0) - { - httpCode = nsError != nil ? getHTTPErrorCode (nsError) : -1; - error = true; - finished = true; - - if (listener != nullptr) - listener->finished (this, ! error); - } - - connectionEvent.signal(); - } - - void didBecomeInvalidWithError() - { - hasBeenDestroyed = true; - destroyEvent.signal(); - } - - //============================================================================== - void notify() - { - if (downloadTask == nullptr) return; - - if (NSError* error = [downloadTask error]) - { - didCompleteWithError (error); - } - else - { - const int64 contentLength = [downloadTask countOfBytesExpectedToReceive]; - - if ([downloadTask state] == NSURLSessionTaskStateCompleted) - didWriteData (contentLength, contentLength); - else - didWriteData ([downloadTask countOfBytesReceived], contentLength); - } - } - - static void invokeNotify (const String& identifier) - { - ScopedLock lock (activeSessions.getLock()); - - if (auto* task = activeSessions[identifier]) - task->notify(); - } - - //============================================================================== - struct DelegateClass : public ObjCClass> - { - DelegateClass() : ObjCClass> ("JUCE_URLDelegate_") - { - addIvar ("state"); - - addMethod (@selector (URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:), - didWriteData, "v@:@@qqq"); - addMethod (@selector (URLSession:downloadTask:didFinishDownloadingToURL:), didFinishDownloadingToURL, "v@:@@@"); - addMethod (@selector (URLSession:task:didCompleteWithError:), didCompleteWithError, "v@:@@@"); - addMethod (@selector (URLSession:didBecomeInvalidWithError:), didBecomeInvalidWithError, "v@:@@@"); - - registerClass(); - } - - static void setState (id self, BackgroundDownloadTask* state) { object_setInstanceVariable (self, "state", state); } - static BackgroundDownloadTask* getState (id self) { return getIvar (self, "state"); } - - private: - static void didWriteData (id self, SEL, NSURLSession*, NSURLSessionDownloadTask*, int64_t, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) - { - if (auto state = getState (self)) - state->didWriteData (totalBytesWritten, totalBytesExpectedToWrite); - } - - static void didFinishDownloadingToURL (id self, SEL, NSURLSession*, NSURLSessionDownloadTask*, NSURL* location) - { - if (auto state = getState (self)) - state->didFinishDownloadingToURL (location); - } - - static void didCompleteWithError (id self, SEL, NSURLSession*, NSURLSessionTask*, NSError* nsError) - { - if (auto state = getState (self)) - state->didCompleteWithError (nsError); - } - - static void didBecomeInvalidWithError (id self, SEL, NSURLSession*, NSURLSessionTask*, NSError*) - { - if (auto state = getState (self)) - state->didBecomeInvalidWithError(); - } - }; -}; - -HashMap BackgroundDownloadTask::activeSessions; - -std::unique_ptr URL::downloadToFile (const File& targetLocation, const DownloadTaskOptions& options) -{ - auto downloadTask = std::make_unique (*this, targetLocation, options); - - if (downloadTask->initOK() && downloadTask->connect()) - return downloadTask; - - return nullptr; -} - -void URL::DownloadTask::juce_iosURLSessionNotify (const String& identifier) -{ - BackgroundDownloadTask::invokeNotify (identifier); -} -#else -std::unique_ptr URL::downloadToFile (const File& targetLocation, const DownloadTaskOptions& options) -{ - return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, options); -} -#endif - -//============================================================================== -#else - -// This version is only used for backwards-compatibility with older OSX targets, -// so we'll turn off deprecation warnings. This code will be removed at some point -// in the future. - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated") - -//============================================================================== -class URLConnectionState : public Thread -{ -public: - URLConnectionState (NSURLRequest* req, const int maxRedirects) - : Thread ("http connection"), - request ([req retain]), - data ([[NSMutableData data] retain]), - numRedirectsToFollow (maxRedirects) - { - static DelegateClass cls; - delegate = [cls.createInstance() init]; - DelegateClass::setState (delegate, this); - } - - ~URLConnectionState() override - { - stop(); - - [connection release]; - [request release]; - [headers release]; - [delegate release]; - [data release]; - } - - bool start (WebInputStream& inputStream, WebInputStream::Listener* listener) - { - startThread(); - - while (isThreadRunning() && ! initialised) - { - if (listener != nullptr) - if (! listener->postDataSendProgress (inputStream, latestTotalBytes, (int) [[request HTTPBody] length])) - return false; - - Thread::sleep (1); - } + NSURLSession* session = nil; + NSURLSessionDownloadTask* downloadTask = nil; + bool connectFinished = false, hasBeenDestroyed = false; + Atomic calledComplete; + WaitableEvent connectionEvent, destroyEvent; + String uniqueIdentifier; - return connection != nil && ! hasFailed; - } + static HashMap activeSessions; - void stop() + void didWriteData (int64 totalBytesWritten, int64 totalBytesExpectedToWrite) { - { - const ScopedLock dLock (dataLock); - const ScopedLock connectionLock (createConnectionLock); + downloaded = totalBytesWritten; - hasBeenCancelled = true; + if (contentLength == -1) + contentLength = totalBytesExpectedToWrite; - if (connection != nil) - [connection cancel]; - } + if (connectFinished && error == false && finished == false && listener != nullptr) + listener->progress (this, totalBytesWritten, contentLength); - stopThread (10000); + connectionEvent.signal(); } - void cancel() + void didFinishDownloadingToURL (NSURL* location) { - hasFinished = hasFailed = true; - stop(); - } + auto* fileManager = [NSFileManager defaultManager]; + error = ([fileManager moveItemAtURL: location + toURL: createNSURLFromFile (targetLocation) + error: nil] == NO); + httpCode = 200; + finished = true; - int read (char* dest, int numBytes) - { - int numDone = 0; + connectionEvent.signal(); - while (numBytes > 0) + if (listener != nullptr && calledComplete.exchange (1) == 0) { - const ScopedLock sl (dataLock); - auto available = jmin (numBytes, (int) [data length]); - - if (available > 0) + if (contentLength > 0 && downloaded < contentLength) { - [data getBytes: dest length: (NSUInteger) available]; - [data replaceBytesInRange: NSMakeRange (0, (NSUInteger) available) withBytes: nil length: 0]; - - numDone += available; - numBytes -= available; - dest += available; + downloaded = contentLength; + listener->progress (this, downloaded, contentLength); } - else - { - if (hasFailed || hasFinished) - break; - const ScopedUnlock sul (dataLock); - Thread::sleep (1); - } + listener->finished (this, !error); } - - return numDone; } - void didReceiveResponse (NSURLResponse* response) + static int getHTTPErrorCode (NSError* nsError) { + // see https://developer.apple.com/reference/foundation/nsurlsessiondownloadtask?language=objc + switch ([nsError code]) { - const ScopedLock sl (dataLock); - [data setLength: 0]; - } - - contentLength = [response expectedContentLength]; - - [headers release]; - headers = nil; - - if ([response isKindOfClass: [NSHTTPURLResponse class]]) - { - NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response; - headers = [[httpResponse allHeaderFields] retain]; - statusCode = (int) [httpResponse statusCode]; + case NSURLErrorUserAuthenticationRequired: return 401; + case NSURLErrorNoPermissionsToReadFile: return 403; + case NSURLErrorFileDoesNotExist: return 404; + default: return 500; } - - initialised = true; } - NSURLRequest* willSendRequest (NSURLRequest* newRequest, NSURLResponse* redirectResponse) + void didCompleteWithError (NSError* nsError) { - if (redirectResponse != nullptr) + if (calledComplete.exchange (1) == 0) { - if (numRedirects >= numRedirectsToFollow) - return nil; // Cancel redirect and allow connection to continue + httpCode = nsError != nil ? getHTTPErrorCode (nsError) : -1; + error = true; + finished = true; - ++numRedirects; + if (listener != nullptr) + listener->finished (this, ! error); } - return newRequest; - } - - void didFailWithError (NSError* error) - { - DBG (nsStringToJuce ([error description])); ignoreUnused (error); - nsUrlErrorCode = [error code]; - hasFailed = true; - initialised = true; - signalThreadShouldExit(); - } - - void didReceiveData (NSData* newData) - { - const ScopedLock sl (dataLock); - [data appendData: newData]; - initialised = true; + connectionEvent.signal(); } - void didSendBodyData (NSInteger totalBytesWritten, NSInteger /*totalBytesExpected*/) + void didBecomeInvalidWithError() { - latestTotalBytes = static_cast (totalBytesWritten); + hasBeenDestroyed = true; + destroyEvent.signal(); } - void finishedLoading() + //============================================================================== + void notify() { - hasFinished = true; - initialised = true; - signalThreadShouldExit(); - } + if (downloadTask == nullptr) return; - void run() override - { + if (NSError* error = [downloadTask error]) { - const ScopedLock lock (createConnectionLock); - - if (hasBeenCancelled) - return; - - connection = [[NSURLConnection alloc] initWithRequest: request - delegate: delegate]; + didCompleteWithError (error); } - - while (! threadShouldExit()) + else { - JUCE_AUTORELEASEPOOL - { - [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; - } + const int64 contentLength = [downloadTask countOfBytesExpectedToReceive]; + + if ([downloadTask state] == NSURLSessionTaskStateCompleted) + didWriteData (contentLength, contentLength); + else + didWriteData ([downloadTask countOfBytesReceived], contentLength); } } - int64 contentLength = -1; - CriticalSection dataLock; - NSObject* delegate = nil; - NSURLRequest* request = nil; - NSURLConnection* connection = nil; - NSMutableData* data = nil; - NSDictionary* headers = nil; - NSInteger nsUrlErrorCode = 0; - int statusCode = 0; - std::atomic initialised { false }, hasFailed { false }, hasFinished { false }; - const int numRedirectsToFollow; - int numRedirects = 0; - int latestTotalBytes = 0; - CriticalSection createConnectionLock; - bool hasBeenCancelled = false; + static void invokeNotify (const String& identifier) + { + ScopedLock lock (activeSessions.getLock()); + + if (auto* task = activeSessions[identifier]) + task->notify(); + } -private: //============================================================================== - struct DelegateClass : public ObjCClass + struct DelegateClass : public ObjCClass> { - DelegateClass() : ObjCClass ("JUCENetworkDelegate_") + DelegateClass() : ObjCClass> ("JUCE_URLDelegate_") { - addIvar ("state"); + addIvar ("state"); - addMethod (@selector (connection:didReceiveResponse:), didReceiveResponse, "v@:@@"); - addMethod (@selector (connection:didFailWithError:), didFailWithError, "v@:@@"); - addMethod (@selector (connection:didReceiveData:), didReceiveData, "v@:@@"); - addMethod (@selector (connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:), - connectionDidSendBodyData, "v@:@iii"); - addMethod (@selector (connectionDidFinishLoading:), connectionDidFinishLoading, "v@:@"); - addMethod (@selector (connection:willSendRequest:redirectResponse:), willSendRequest, "@@:@@@"); + addMethod (@selector (URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:), + didWriteData, "v@:@@qqq"); + addMethod (@selector (URLSession:downloadTask:didFinishDownloadingToURL:), didFinishDownloadingToURL, "v@:@@@"); + addMethod (@selector (URLSession:task:didCompleteWithError:), didCompleteWithError, "v@:@@@"); + addMethod (@selector (URLSession:didBecomeInvalidWithError:), didBecomeInvalidWithError, "v@:@@@"); registerClass(); } - static void setState (id self, URLConnectionState* state) { object_setInstanceVariable (self, "state", state); } - static URLConnectionState* getState (id self) { return getIvar (self, "state"); } + static void setState (id self, BackgroundDownloadTask* state) { object_setInstanceVariable (self, "state", state); } + static BackgroundDownloadTask* getState (id self) { return getIvar (self, "state"); } private: - static void didReceiveResponse (id self, SEL, NSURLConnection*, NSURLResponse* response) + static void didWriteData (id self, SEL, NSURLSession*, NSURLSessionDownloadTask*, int64_t, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { - getState (self)->didReceiveResponse (response); + if (auto state = getState (self)) + state->didWriteData (totalBytesWritten, totalBytesExpectedToWrite); } - static void didFailWithError (id self, SEL, NSURLConnection*, NSError* error) + static void didFinishDownloadingToURL (id self, SEL, NSURLSession*, NSURLSessionDownloadTask*, NSURL* location) { - getState (self)->didFailWithError (error); + if (auto state = getState (self)) + state->didFinishDownloadingToURL (location); } - static void didReceiveData (id self, SEL, NSURLConnection*, NSData* newData) + static void didCompleteWithError (id self, SEL, NSURLSession*, NSURLSessionTask*, NSError* nsError) { - getState (self)->didReceiveData (newData); + if (auto state = getState (self)) + state->didCompleteWithError (nsError); } - static NSURLRequest* willSendRequest (id self, SEL, NSURLConnection*, NSURLRequest* request, NSURLResponse* response) + static void didBecomeInvalidWithError (id self, SEL, NSURLSession*, NSURLSessionTask*, NSError*) { - return getState (self)->willSendRequest (request, response); + if (auto state = getState (self)) + state->didBecomeInvalidWithError(); } + }; +}; - static void connectionDidSendBodyData (id self, SEL, NSURLConnection*, NSInteger, NSInteger totalBytesWritten, NSInteger totalBytesExpected) - { - getState (self)->didSendBodyData (totalBytesWritten, totalBytesExpected); - } +HashMap BackgroundDownloadTask::activeSessions; - static void connectionDidFinishLoading (id self, SEL, NSURLConnection*) - { - getState (self)->finishedLoading(); - } - }; +std::unique_ptr URL::downloadToFile (const File& targetLocation, const DownloadTaskOptions& options) +{ + auto downloadTask = std::make_unique (*this, targetLocation, options); - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState) -}; + if (downloadTask->initOK() && downloadTask->connect()) + return downloadTask; + return nullptr; +} + +void URL::DownloadTask::juce_iosURLSessionNotify (const String& identifier) +{ + BackgroundDownloadTask::invokeNotify (identifier); +} +#else std::unique_ptr URL::downloadToFile (const File& targetLocation, const DownloadTaskOptions& options) { return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, options); } - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - #endif - //============================================================================== class WebInputStream::Pimpl { @@ -977,28 +970,28 @@ public: if (! connection->start (owner, webInputListener)) { - // Workaround for deployment targets below 10.10 where HTTPS POST requests with keep-alive fail with the NSURLErrorNetworkConnectionLost error code. - #if ! (JUCE_IOS || (defined (MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10)) - if (numRetries == 0 && connection->nsUrlErrorCode == NSURLErrorNetworkConnectionLost) - { - connection.reset(); + connection.reset(); + + if (@available (macOS 10.10, *)) + return false; + + // Workaround for macOS versions below 10.10 where HTTPS POST requests with keep-alive + // fail with the NSURLErrorNetworkConnectionLost error code. + if (numRetries == 0 && connection->getErrorCode() == NSURLErrorNetworkConnectionLost) return connect (webInputListener, ++numRetries); - } - #endif - connection.reset(); return false; } - if (connection->headers != nil) + if (auto* connectionHeaders = connection->getHeaders()) { - statusCode = connection->statusCode; + statusCode = connection->getStatusCode(); - NSEnumerator* enumerator = [connection->headers keyEnumerator]; + NSEnumerator* enumerator = [connectionHeaders keyEnumerator]; while (NSString* key = [enumerator nextObject]) responseHeaders.set (nsStringToJuce (key), - nsStringToJuce ((NSString*) [connection->headers objectForKey: key])); + nsStringToJuce ((NSString*) [connectionHeaders objectForKey: key])); return true; } @@ -1038,10 +1031,9 @@ public: StringPairArray getResponseHeaders() const { return responseHeaders; } int getStatusCode() const { return statusCode; } - //============================================================================== - bool isError() const { return (connection == nullptr || connection->headers == nullptr); } - int64 getTotalLength() { return connection == nullptr ? -1 : connection->contentLength; } + bool isError() const { return (connection == nullptr || connection->getHeaders() == nullptr); } + int64 getTotalLength() { return connection == nullptr ? -1 : connection->getContentLength(); } bool isExhausted() { return finished; } int64 getPosition() { return position; } @@ -1089,7 +1081,7 @@ public: private: WebInputStream& owner; URL url; - std::unique_ptr connection; + std::unique_ptr connection; String headers; MemoryBlock postData; int64 position = 0; @@ -1152,7 +1144,12 @@ private: // Workaround for an Apple bug. See https://github.com/AFNetworking/AFNetworking/issues/2334 [req HTTPBody]; - connection.reset (new URLConnectionState (req, numRedirectsToFollow)); + if (@available (macOS 10.10, *)) + connection = std::make_unique (req, numRedirectsToFollow); + #if JUCE_MAC + else + connection = std::make_unique (req, numRedirectsToFollow); + #endif } } } diff --git a/modules/juce_core/native/juce_mac_SystemStats.mm b/modules/juce_core/native/juce_mac_SystemStats.mm index 1420e7a480..6d52c55b6b 100644 --- a/modules/juce_core/native/juce_mac_SystemStats.mm +++ b/modules/juce_core/native/juce_mac_SystemStats.mm @@ -89,15 +89,21 @@ static String getOSXVersion() { JUCE_AUTORELEASEPOOL { - const String systemVersionPlist ("/System/Library/CoreServices/SystemVersion.plist"); - - #if (defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13) - NSError* error = nullptr; - NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL: createNSURLFromFile (systemVersionPlist) - error: &error]; - #else - NSDictionary* dict = [NSDictionary dictionaryWithContentsOfFile: juceStringToNS (systemVersionPlist)]; - #endif + const auto* dict = [] + { + const String systemVersionPlist ("/System/Library/CoreServices/SystemVersion.plist"); + + #if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 + if (@available (macOS 10.13, *)) + { + NSError* error = nullptr; + return [NSDictionary dictionaryWithContentsOfURL: createNSURLFromFile (systemVersionPlist) + error: &error]; + } + #endif + + return [NSDictionary dictionaryWithContentsOfFile: juceStringToNS (systemVersionPlist)]; + }(); if (dict != nullptr) return nsStringToJuce ([dict objectForKey: nsStringLiteral ("ProductVersion")]); diff --git a/modules/juce_gui_basics/native/accessibility/juce_ios_Accessibility.mm b/modules/juce_gui_basics/native/accessibility/juce_ios_Accessibility.mm index 5d646d9f3c..86a13dc03c 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_ios_Accessibility.mm +++ b/modules/juce_gui_basics/native/accessibility/juce_ios_Accessibility.mm @@ -37,10 +37,12 @@ static void juceFreeAccessibilityPlatformSpecificData (UIAccessibilityElement* e namespace juce { -#if (defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0) +#if defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 #define JUCE_IOS_CONTAINER_API_AVAILABLE 1 #endif +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new") + constexpr auto juceUIAccessibilityContainerTypeNone = #if JUCE_IOS_CONTAINER_API_AVAILABLE UIAccessibilityContainerTypeNone; @@ -62,6 +64,8 @@ constexpr auto juceUIAccessibilityContainerTypeList = 2; #endif +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + #define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1 //============================================================================== diff --git a/modules/juce_gui_basics/native/juce_ios_FileChooser.mm b/modules/juce_gui_basics/native/juce_ios_FileChooser.mm index c89117abf8..797a0c03b9 100644 --- a/modules/juce_gui_basics/native/juce_ios_FileChooser.mm +++ b/modules/juce_gui_basics/native/juce_ios_FileChooser.mm @@ -26,7 +26,7 @@ namespace juce { -#if ! (defined (__IPHONE_16_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_16_0) +#if ! (defined (__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0) JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") #define JUCE_DEPRECATION_IGNORED 1 #endif @@ -382,6 +382,4 @@ std::shared_ptr FileChooser::showPlatformDialog (FileChooser JUCE_END_IGNORE_WARNINGS_GCC_LIKE #endif -#undef JUCE_DEPRECATION_IGNORED - } // namespace juce diff --git a/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm index 8414a6a697..d52eadaebc 100644 --- a/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm +++ b/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm @@ -23,7 +23,7 @@ ============================================================================== */ -#if defined (__IPHONE_13_0) +#if defined (__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 #define JUCE_HAS_IOS_POINTER_SUPPORT 1 #else #define JUCE_HAS_IOS_POINTER_SUPPORT 0 @@ -38,15 +38,18 @@ static UIInterfaceOrientation getWindowOrientation() { UIApplication* sharedApplication = [UIApplication sharedApplication]; - #if (defined (__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0) - for (UIScene* scene in [sharedApplication connectedScenes]) - if ([scene isKindOfClass: [UIWindowScene class]]) - return [(UIWindowScene*) scene interfaceOrientation]; + #if defined (__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 + if (@available (iOS 13.0, *)) + { + for (UIScene* scene in [sharedApplication connectedScenes]) + if ([scene isKindOfClass: [UIWindowScene class]]) + return [(UIWindowScene*) scene interfaceOrientation]; + } + #endif - return UIInterfaceOrientationPortrait; - #else + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") return [sharedApplication statusBarOrientation]; - #endif + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } namespace Orientations diff --git a/modules/juce_gui_basics/native/juce_ios_Windowing.mm b/modules/juce_gui_basics/native/juce_ios_Windowing.mm index 6c1ddd2923..41503d2a7b 100644 --- a/modules/juce_gui_basics/native/juce_ios_Windowing.mm +++ b/modules/juce_gui_basics/native/juce_ios_Windowing.mm @@ -787,7 +787,7 @@ static Rectangle getRecommendedWindowBounds() static BorderSize getSafeAreaInsets (float masterScale) { - #if defined (__IPHONE_11_0) + #if defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 if (@available (iOS 11.0, *)) { UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets; @@ -799,7 +799,10 @@ static BorderSize getSafeAreaInsets (float masterScale) } #endif + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") auto statusBarSize = [UIApplication sharedApplication].statusBarFrame.size; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + auto statusBarHeight = jmin (statusBarSize.width, statusBarSize.height); return { roundToInt (statusBarHeight / masterScale), 0, 0, 0 }; diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index 9daf7c2d3c..db9e20086b 100644 --- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -1344,14 +1344,17 @@ public: static NSArray* getSupportedDragTypes() { - const auto type = - #if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13 - NSPasteboardTypeFileURL; - #else - kUTTypeFileURL; - #endif - - return [NSArray arrayWithObjects: (NSString*) type, (NSString*) kPasteboardTypeFileURLPromise, NSPasteboardTypeString, nil]; + const auto type = [] + { + #if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 + if (@available (macOS 10.13, *)) + return NSPasteboardTypeFileURL; + #endif + + return (NSString*) kUTTypeFileURL; + }(); + + return [NSArray arrayWithObjects: type, (NSString*) kPasteboardTypeFileURLPromise, NSPasteboardTypeString, nil]; } BOOL sendDragCallback (const int type, id sender) diff --git a/modules/juce_gui_extra/native/juce_mac_AppleRemote.mm b/modules/juce_gui_extra/native/juce_mac_AppleRemote.mm index ec8d5d8e37..e1e6459a4b 100644 --- a/modules/juce_gui_extra/native/juce_mac_AppleRemote.mm +++ b/modules/juce_gui_extra/native/juce_mac_AppleRemote.mm @@ -47,12 +47,15 @@ namespace io_iterator_t iter = 0; io_object_t iod = 0; - const auto defaultPort = - #if defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_12_0 - kIOMainPortDefault; - #else - kIOMasterPortDefault; - #endif + const auto defaultPort = [] + { + #if defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0 + if (@available (macOS 12.0, *)) + return kIOMainPortDefault; + #endif + + return kIOMasterPortDefault; + }(); if (IOServiceGetMatchingServices (defaultPort, dict, &iter) == kIOReturnSuccess && iter != 0) diff --git a/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp b/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp index 4427758c28..cd8cbf0a01 100644 --- a/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp +++ b/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp @@ -26,7 +26,7 @@ namespace juce { -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wdeprecated-declarations") +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new", "-Wdeprecated-declarations") extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId, int topLevelIndex, bool addDelegate); diff --git a/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm b/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm index 975fc3e99f..bd179c928a 100644 --- a/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm +++ b/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm @@ -26,11 +26,7 @@ namespace juce { -#if JUCE_IOS || (defined (MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) - #define JUCE_USE_WKWEBVIEW 1 -#endif - -#if (defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) +#if JUCE_MAC && defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 #define WKWEBVIEW_OPENPANEL_SUPPORTED 1 #endif @@ -115,41 +111,39 @@ static NSMutableURLRequest* getRequestForURL (const String& url, const StringArr } #if JUCE_MAC - -#if JUCE_USE_WKWEBVIEW - using WebViewBase = ObjCClass; -#else - using WebViewBase = ObjCClass; -#endif - -struct WebViewKeyEquivalentResponder : public WebViewBase +template +struct WebViewKeyEquivalentResponder : public ObjCClass { WebViewKeyEquivalentResponder() - : WebViewBase ("WebViewKeyEquivalentResponder_") + : ObjCClass ("WebViewKeyEquivalentResponder_") { - addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@"); - registerClass(); + ObjCClass::addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@"); + ObjCClass::registerClass(); } private: static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event) { - NSResponder* first = [[self window] firstResponder]; + const auto isCommandDown = [event] + { + const auto modifierFlags = [event modifierFlags]; - #if (defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) - constexpr auto mask = NSEventModifierFlagDeviceIndependentFlagsMask; - constexpr auto key = NSEventModifierFlagCommand; - #else - constexpr auto mask = NSDeviceIndependentModifierFlagsMask; - constexpr auto key = NSCommandKeyMask; - #endif + #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 + if (@available (macOS 10.12, *)) + return (modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagCommand; + #endif - if (([event modifierFlags] & mask) == key) + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + return (modifierFlags & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + }(); + + if (isCommandDown) { auto sendAction = [&] (SEL actionSelector) -> BOOL { return [NSApp sendAction: actionSelector - to: first + to: [[self window] firstResponder] from: self]; }; @@ -159,13 +153,122 @@ private: if ([[event charactersIgnoringModifiers] isEqualToString: @"a"]) return sendAction (@selector (selectAll:)); } - return sendSuperclassMessage (self, selector, event); + return ObjCClass::template sendSuperclassMessage (self, selector, event); } }; -#endif +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") +struct DownloadClickDetectorClass : public ObjCClass +{ + DownloadClickDetectorClass() : ObjCClass ("JUCEWebClickDetector_") + { + addIvar ("owner"); + + addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:), + decidePolicyForNavigationAction, "v@:@@@@@"); + addMethod (@selector (webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:), + decidePolicyForNewWindowAction, "v@:@@@@@"); + addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@"); + addMethod (@selector (webView:didFailLoadWithError:forFrame:), didFailLoadWithError, "v@:@@@"); + addMethod (@selector (webView:didFailProvisionalLoadWithError:forFrame:), didFailLoadWithError, "v@:@@@"); + addMethod (@selector (webView:willCloseFrame:), willCloseFrame, "v@:@@"); + addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:), runOpenPanel, "v@:@@", @encode (BOOL)); + + registerClass(); + } + + static void setOwner (id self, WebBrowserComponent* owner) { object_setInstanceVariable (self, "owner", owner); } + static WebBrowserComponent* getOwner (id self) { return getIvar (self, "owner"); } + +private: + static String getOriginalURL (NSDictionary* actionInformation) + { + if (NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")]) + return nsStringToJuce ([url absoluteString]); + + return {}; + } + + static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation, + NSURLRequest*, WebFrame*, id listener) + { + if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation))) + [listener use]; + else + [listener ignore]; + } + + static void decidePolicyForNewWindowAction (id self, SEL, WebView*, NSDictionary* actionInformation, + NSURLRequest*, NSString*, id listener) + { + getOwner (self)->newWindowAttemptingToLoad (getOriginalURL (actionInformation)); + [listener ignore]; + } + + static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame) + { + if ([frame isEqual: [sender mainFrame]]) + { + NSURL* url = [[[frame dataSource] request] URL]; + getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString])); + } + } -#if JUCE_USE_WKWEBVIEW + static void didFailLoadWithError (id self, SEL, WebView* sender, NSError* error, WebFrame* frame) + { + if ([frame isEqual: [sender mainFrame]] && error != nullptr && [error code] != NSURLErrorCancelled) + { + auto errorString = nsStringToJuce ([error localizedDescription]); + bool proceedToErrorPage = getOwner (self)->pageLoadHadNetworkError (errorString); + + // WebKit doesn't have an internal error page, so make a really simple one ourselves + if (proceedToErrorPage) + getOwner (self)->goToURL ("data:text/plain;charset=UTF-8," + errorString); + } + } + + static void willCloseFrame (id self, SEL, WebView*, WebFrame*) + { + getOwner (self)->windowCloseRequest(); + } + + static void runOpenPanel (id, SEL, WebView*, id resultListener, BOOL allowMultipleFiles) + { + struct DeletedFileChooserWrapper : private DeletedAtShutdown + { + DeletedFileChooserWrapper (std::unique_ptr fc, id rl) + : chooser (std::move (fc)), listener (rl) + { + [listener retain]; + } + + ~DeletedFileChooserWrapper() + { + [listener release]; + } + + std::unique_ptr chooser; + id listener; + }; + + auto chooser = std::make_unique (TRANS("Select the file you want to upload..."), + File::getSpecialLocation (File::userHomeDirectory), "*"); + auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), resultListener); + + auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles + | (allowMultipleFiles ? FileBrowserComponent::canSelectMultipleItems : 0); + + wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&) + { + for (auto& f : wrapper->chooser->getResults()) + [wrapper->listener chooseFilename: juceStringToNS (f.getFullPathName())]; + + delete wrapper; + }); + } +}; +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif struct WebViewDelegateClass : public ObjCClass { @@ -182,7 +285,8 @@ struct WebViewDelegateClass : public ObjCClass windowFeatures:), createWebView, "@@:@@@@"); #if WKWEBVIEW_OPENPANEL_SUPPORTED - addMethod (@selector (webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:), runOpenPanel, "v@:@@@@"); + if (@available (macOS 10.12, *)) + addMethod (@selector (webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:), runOpenPanel, "v@:@@@@"); #endif registerClass(); @@ -242,6 +346,7 @@ private: } #if WKWEBVIEW_OPENPANEL_SUPPORTED + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new") static void runOpenPanel (id, SEL, WKWebView*, WKOpenPanelParameters* parameters, WKFrameInfo*, void (^completionHandler)(NSArray*)) { @@ -285,10 +390,13 @@ private: auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles | ([parameters allowsMultipleSelection] ? FileBrowserComponent::canSelectMultipleItems : 0); - #if (defined (MAC_OS_X_VERSION_10_14) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14) - if ([parameters allowsDirectories]) - flags |= FileBrowserComponent::canSelectDirectories; - #endif + #if (defined (MAC_OS_X_VERSION_10_14) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14) + if (@available (macOS 10.14, *)) + { + if ([parameters allowsDirectories]) + flags |= FileBrowserComponent::canSelectDirectories; + } + #endif wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&) { @@ -302,242 +410,186 @@ private: delete wrapper; }); } + JUCE_END_IGNORE_WARNINGS_GCC_LIKE #endif }; //============================================================================== -class WebBrowserComponent::Pimpl - #if JUCE_MAC - : public NSViewComponent - #else - : public UIViewComponent - #endif +struct WebViewBase +{ + virtual ~WebViewBase() = default; + + virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0; + virtual void goBack() = 0; + virtual void goForward() = 0; + virtual void stop() = 0; + virtual void refresh() = 0; + + virtual id getWebView() = 0; +}; + +#if JUCE_MAC +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") +class WebViewImpl : public WebViewBase { public: - Pimpl (WebBrowserComponent* owner) + WebViewImpl (WebBrowserComponent* owner) { - #if JUCE_MAC - static WebViewKeyEquivalentResponder webviewClass; - webView = (WKWebView*) webviewClass.createInstance(); - - webView = [webView initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]; - #else - webView = [[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)]; - #endif + static WebViewKeyEquivalentResponder webviewClass; + webView = (WebView*) webviewClass.createInstance(); - static WebViewDelegateClass cls; - webViewDelegate = [cls.createInstance() init]; - WebViewDelegateClass::setOwner (webViewDelegate, owner); + webView = [webView initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f) + frameName: nsEmptyString() + groupName: nsEmptyString()]; - [webView setNavigationDelegate: webViewDelegate]; - [webView setUIDelegate: webViewDelegate]; + static DownloadClickDetectorClass cls; + clickListener = [cls.createInstance() init]; + DownloadClickDetectorClass::setOwner (clickListener, owner); - setView (webView); + [webView setPolicyDelegate: clickListener]; + [webView setFrameLoadDelegate: clickListener]; + [webView setUIDelegate: clickListener]; } - ~Pimpl() + ~WebViewImpl() override { - [webView setNavigationDelegate: nil]; - [webView setUIDelegate: nil]; - - [webViewDelegate release]; + [webView setPolicyDelegate: nil]; + [webView setFrameLoadDelegate: nil]; + [webView setUIDelegate: nil]; - setView (nil); + [clickListener release]; } void goToURL (const String& url, const StringArray* headers, - const MemoryBlock* postData) + const MemoryBlock* postData) override { - auto trimmed = url.trimStart(); - - if (trimmed.startsWithIgnoreCase ("javascript:")) + if (url.trimStart().startsWithIgnoreCase ("javascript:")) { - [webView evaluateJavaScript: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false)) - completionHandler: nil]; - + [webView stringByEvaluatingJavaScriptFromString: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))]; return; } stop(); - if (trimmed.startsWithIgnoreCase ("file:")) + auto getRequest = [&]() -> NSMutableURLRequest* { - auto file = URL (url).getLocalFile(); + if (url.trimStart().startsWithIgnoreCase ("file:")) + { + auto file = URL (url).getLocalFile(); - if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]) - [webView loadFileURL: appendParametersToFileURL (url, nsUrl) allowingReadAccessToURL: nsUrl]; - } - else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData)) - { - [webView loadRequest: request]; - } - } + if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]) + return [NSMutableURLRequest requestWithURL: appendParametersToFileURL (url, nsUrl) + cachePolicy: NSURLRequestUseProtocolCachePolicy + timeoutInterval: 30.0]; - void goBack() { [webView goBack]; } - void goForward() { [webView goForward]; } + return nullptr; + } - void stop() { [webView stopLoading]; } - void refresh() { [webView reload]; } + return getRequestForURL (url, headers, postData); + }; -private: - WKWebView* webView = nil; - id webViewDelegate; -}; + if (NSMutableURLRequest* request = getRequest()) + [[webView mainFrame] loadRequest: request]; + } -#else + void goBack() override { [webView goBack]; } + void goForward() override { [webView goForward]; } -#if JUCE_MAC + void stop() override { [webView stopLoading: nil]; } + void refresh() override { [webView reload: nil]; } -struct DownloadClickDetectorClass : public ObjCClass -{ - DownloadClickDetectorClass() : ObjCClass ("JUCEWebClickDetector_") - { - addIvar ("owner"); + id getWebView() override { return webView; } - addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:), - decidePolicyForNavigationAction, "v@:@@@@@"); - addMethod (@selector (webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:), - decidePolicyForNewWindowAction, "v@:@@@@@"); - addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@"); - addMethod (@selector (webView:didFailLoadWithError:forFrame:), didFailLoadWithError, "v@:@@@"); - addMethod (@selector (webView:didFailProvisionalLoadWithError:forFrame:), didFailLoadWithError, "v@:@@@"); - addMethod (@selector (webView:willCloseFrame:), willCloseFrame, "v@:@@"); - addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:), runOpenPanel, "v@:@@", @encode (BOOL)); - - registerClass(); + void mouseMove (const MouseEvent&) + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + // WebKit doesn't capture mouse-moves itself, so it seems the only way to make + // them work is to push them via this non-public method.. + if ([webView respondsToSelector: @selector (_updateMouseoverWithFakeEvent)]) + [webView performSelector: @selector (_updateMouseoverWithFakeEvent)]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } - static void setOwner (id self, WebBrowserComponent* owner) { object_setInstanceVariable (self, "owner", owner); } - static WebBrowserComponent* getOwner (id self) { return getIvar (self, "owner"); } - private: - static String getOriginalURL (NSDictionary* actionInformation) - { - if (NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")]) - return nsStringToJuce ([url absoluteString]); - - return {}; - } + WebView* webView = nil; + id clickListener; +}; +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif - static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation, - NSURLRequest*, WebFrame*, id listener) +class WKWebViewImpl : public WebViewBase +{ +public: + WKWebViewImpl (WebBrowserComponent* owner) { - if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation))) - [listener use]; - else - [listener ignore]; - } + #if JUCE_MAC + static WebViewKeyEquivalentResponder webviewClass; + webView = (WKWebView*) webviewClass.createInstance(); - static void decidePolicyForNewWindowAction (id self, SEL, WebView*, NSDictionary* actionInformation, - NSURLRequest*, NSString*, id listener) - { - getOwner (self)->newWindowAttemptingToLoad (getOriginalURL (actionInformation)); - [listener ignore]; - } + webView = [webView initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]; + #else + webView = [[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)]; + #endif - static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame) - { - if ([frame isEqual: [sender mainFrame]]) - { - NSURL* url = [[[frame dataSource] request] URL]; - getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString])); - } + static WebViewDelegateClass cls; + webViewDelegate = [cls.createInstance() init]; + WebViewDelegateClass::setOwner (webViewDelegate, owner); + + [webView setNavigationDelegate: webViewDelegate]; + [webView setUIDelegate: webViewDelegate]; } - static void didFailLoadWithError (id self, SEL, WebView* sender, NSError* error, WebFrame* frame) + ~WKWebViewImpl() override { - if ([frame isEqual: [sender mainFrame]] && error != nullptr && [error code] != NSURLErrorCancelled) - { - auto errorString = nsStringToJuce ([error localizedDescription]); - bool proceedToErrorPage = getOwner (self)->pageLoadHadNetworkError (errorString); + [webView setNavigationDelegate: nil]; + [webView setUIDelegate: nil]; - // WebKit doesn't have an internal error page, so make a really simple one ourselves - if (proceedToErrorPage) - getOwner (self)->goToURL ("data:text/plain;charset=UTF-8," + errorString); - } + [webViewDelegate release]; } - static void willCloseFrame (id self, SEL, WebView*, WebFrame*) + void goToURL (const String& url, + const StringArray* headers, + const MemoryBlock* postData) override { - getOwner (self)->windowCloseRequest(); - } + auto trimmed = url.trimStart(); - static void runOpenPanel (id, SEL, WebView*, id resultListener, BOOL allowMultipleFiles) - { - struct DeletedFileChooserWrapper : private DeletedAtShutdown + if (trimmed.startsWithIgnoreCase ("javascript:")) { - DeletedFileChooserWrapper (std::unique_ptr fc, id rl) - : chooser (std::move (fc)), listener (rl) - { - [listener retain]; - } - - ~DeletedFileChooserWrapper() - { - [listener release]; - } - - std::unique_ptr chooser; - id listener; - }; + [webView evaluateJavaScript: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false)) + completionHandler: nil]; - auto chooser = std::make_unique (TRANS("Select the file you want to upload..."), - File::getSpecialLocation (File::userHomeDirectory), "*"); - auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), resultListener); + return; + } - auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles - | (allowMultipleFiles ? FileBrowserComponent::canSelectMultipleItems : 0); + stop(); - wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&) + if (trimmed.startsWithIgnoreCase ("file:")) { - for (auto& f : wrapper->chooser->getResults()) - [wrapper->listener chooseFilename: juceStringToNS (f.getFullPathName())]; + auto file = URL (url).getLocalFile(); - delete wrapper; - }); + if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]) + [webView loadFileURL: appendParametersToFileURL (url, nsUrl) allowingReadAccessToURL: nsUrl]; + } + else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData)) + { + [webView loadRequest: request]; + } } -}; - -#else -struct WebViewDelegateClass : public ObjCClass -{ - WebViewDelegateClass() : ObjCClass ("JUCEWebViewDelegate_") - { - addIvar ("owner"); + void goBack() override { [webView goBack]; } + void goForward() override { [webView goForward]; } - addMethod (@selector (gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:), - shouldRecognizeSimultaneouslyWithGestureRecognizer, "c@:@@"); + void stop() override { [webView stopLoading]; } + void refresh() override { [webView reload]; } - addMethod (@selector (webView:shouldStartLoadWithRequest:navigationType:), shouldStartLoadWithRequest, "c@:@@@"); - addMethod (@selector (webViewDidFinishLoad:), webViewDidFinishLoad, "v@:@"); - - registerClass(); - } - - static void setOwner (id self, WebBrowserComponent* owner) { object_setInstanceVariable (self, "owner", owner); } - static WebBrowserComponent* getOwner (id self) { return getIvar (self, "owner"); } + id getWebView() override { return webView; } private: - static BOOL shouldRecognizeSimultaneouslyWithGestureRecognizer (id, SEL, UIGestureRecognizer*, UIGestureRecognizer*) - { - return YES; - } - - static BOOL shouldStartLoadWithRequest (id self, SEL, UIWebView*, NSURLRequest* request, UIWebViewNavigationType) - { - return getOwner (self)->pageAboutToLoad (nsStringToJuce ([[request URL] absoluteString])); - } - - static void webViewDidFinishLoad (id self, SEL, UIWebView* webView) - { - getOwner (self)->pageFinishedLoading (nsStringToJuce ([[[webView request] URL] absoluteString])); - } + WKWebView* webView = nil; + id webViewDelegate; }; -#endif - //============================================================================== class WebBrowserComponent::Pimpl #if JUCE_MAC @@ -549,47 +601,19 @@ class WebBrowserComponent::Pimpl public: Pimpl (WebBrowserComponent* owner) { + if (@available (macOS 10.10, *)) + webView = std::make_unique (owner); #if JUCE_MAC - static WebViewKeyEquivalentResponder webviewClass; - webView = (WebView*) webviewClass.createInstance(); - - webView = [webView initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f) - frameName: nsEmptyString() - groupName: nsEmptyString()]; - - static DownloadClickDetectorClass cls; - clickListener = [cls.createInstance() init]; - DownloadClickDetectorClass::setOwner (clickListener, owner); - - [webView setPolicyDelegate: clickListener]; - [webView setFrameLoadDelegate: clickListener]; - [webView setUIDelegate: clickListener]; - #else - webView = [[UIWebView alloc] initWithFrame: CGRectMake (0, 0, 1.0f, 1.0f)]; - - static WebViewDelegateClass cls; - webViewDelegate = [cls.createInstance() init]; - WebViewDelegateClass::setOwner (webViewDelegate, owner); - - [webView setDelegate: webViewDelegate]; + else + webView = std::make_unique (owner); #endif - setView (webView); + setView (webView->getWebView()); } ~Pimpl() { - #if JUCE_MAC - [webView setPolicyDelegate: nil]; - [webView setFrameLoadDelegate: nil]; - [webView setUIDelegate: nil]; - - [clickListener release]; - #else - [webView setDelegate: nil]; - [webViewDelegate release]; - #endif - + webView = nullptr; setView (nil); } @@ -597,78 +621,19 @@ public: const StringArray* headers, const MemoryBlock* postData) { - if (url.trimStart().startsWithIgnoreCase ("javascript:")) - { - [webView stringByEvaluatingJavaScriptFromString: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))]; - return; - } - - stop(); - - auto getRequest = [&]() -> NSMutableURLRequest* - { - if (url.trimStart().startsWithIgnoreCase ("file:")) - { - auto file = URL (url).getLocalFile(); - - if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]) - return [NSMutableURLRequest requestWithURL: appendParametersToFileURL (url, nsUrl) - cachePolicy: NSURLRequestUseProtocolCachePolicy - timeoutInterval: 30.0]; - - return nullptr; - } - - return getRequestForURL (url, headers, postData); - }; - - if (NSMutableURLRequest* request = getRequest()) - { - #if JUCE_MAC - [[webView mainFrame] loadRequest: request]; - #else - [webView loadRequest: request]; - #endif - - #if JUCE_IOS - [webView setScalesPageToFit: YES]; - #endif - } + webView->goToURL (url, headers, postData); } - void goBack() { [webView goBack]; } - void goForward() { [webView goForward]; } - - #if JUCE_MAC - void stop() { [webView stopLoading: nil]; } - void refresh() { [webView reload: nil]; } - #else - void stop() { [webView stopLoading]; } - void refresh() { [webView reload]; } - #endif + void goBack() { webView->goBack(); } + void goForward() { webView->goForward(); } - void mouseMove (const MouseEvent&) - { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - // WebKit doesn't capture mouse-moves itself, so it seems the only way to make - // them work is to push them via this non-public method.. - if ([webView respondsToSelector: @selector (_updateMouseoverWithFakeEvent)]) - [webView performSelector: @selector (_updateMouseoverWithFakeEvent)]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } + void stop() { webView->stop(); } + void refresh() { webView->refresh(); } private: - #if JUCE_MAC - WebView* webView = nil; - id clickListener; - #else - UIWebView* webView = nil; - id webViewDelegate; - #endif + std::unique_ptr webView; }; -#endif - //============================================================================== WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden) : unloadPageWhenHidden (unloadWhenHidden) diff --git a/modules/juce_opengl/native/juce_OpenGL_osx.h b/modules/juce_opengl/native/juce_OpenGL_osx.h index 773ec27966..5dce10b35b 100644 --- a/modules/juce_opengl/native/juce_OpenGL_osx.h +++ b/modules/juce_opengl/native/juce_OpenGL_osx.h @@ -206,11 +206,7 @@ public: jassert (isPositiveAndBelow (numFramesPerSwap, 2)); [renderContext setValues: (const GLint*) &numFramesPerSwap - #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12 - forParameter: NSOpenGLContextParameterSwapInterval]; - #else - forParameter: NSOpenGLCPSwapInterval]; - #endif + forParameter: getSwapIntervalParameter()]; updateMinSwapTime(); @@ -221,11 +217,7 @@ public: { GLint numFrames = 0; [renderContext getValues: &numFrames - #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12 - forParameter: NSOpenGLContextParameterSwapInterval]; - #else - forParameter: NSOpenGLCPSwapInterval]; - #endif + forParameter: getSwapIntervalParameter()]; return numFrames; } @@ -242,6 +234,16 @@ public: minSwapTimeMs = static_cast (numFramesPerSwap * 1000 * videoRefreshPeriodS); } + static NSOpenGLContextParameter getSwapIntervalParameter() + { + #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 + if (@available (macOS 10.12, *)) + return NSOpenGLContextParameterSwapInterval; + #endif + + return NSOpenGLCPSwapInterval; + } + NSOpenGLContext* renderContext = nil; NSOpenGLView* view = nil; ReferenceCountedObjectPtr viewAttachment; diff --git a/modules/juce_video/capture/juce_CameraDevice.cpp b/modules/juce_video/capture/juce_CameraDevice.cpp index a9d76008aa..4fb3620b9a 100644 --- a/modules/juce_video/capture/juce_CameraDevice.cpp +++ b/modules/juce_video/capture/juce_CameraDevice.cpp @@ -31,7 +31,7 @@ namespace juce #elif JUCE_WINDOWS #include "../native/juce_win32_CameraDevice.h" #elif JUCE_IOS - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new") + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new") #include "../native/juce_ios_CameraDevice.h" diff --git a/modules/juce_video/native/juce_ios_CameraDevice.h b/modules/juce_video/native/juce_ios_CameraDevice.h index 724d951a83..978e0b9328 100644 --- a/modules/juce_video/native/juce_ios_CameraDevice.h +++ b/modules/juce_video/native/juce_ios_CameraDevice.h @@ -23,9 +23,9 @@ ============================================================================== */ -#if (defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_10_0) +#if (defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0) JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - #define JUCE_DEPRECATION_IGNORED 1 + #define JUCE_USE_NEW_CAMERA_API 1 #endif struct CameraDevice::Pimpl @@ -141,7 +141,7 @@ struct CameraDevice::Pimpl private: static NSArray* getDevices() { - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + #if JUCE_USE_NEW_CAMERA_API if (@available (iOS 10.0, *)) { std::unique_ptr, NSObjectDeleter> deviceTypes ([[NSMutableArray alloc] initWithCapacity: 2]); @@ -207,7 +207,7 @@ private: JUCE_CAMERA_LOG ("Supports custom exposure: " + String ((int)[device isExposureModeSupported: AVCaptureExposureModeCustom])); JUCE_CAMERA_LOG ("Supports point of interest exposure: " + String ((int)device.exposurePointOfInterestSupported)); - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + #if JUCE_USE_NEW_CAMERA_API if (@available (iOS 10.0, *)) { JUCE_CAMERA_LOG ("Device type: " + nsStringToJuce (device.deviceType)); @@ -238,7 +238,7 @@ private: { JUCE_CAMERA_LOG ("Media type: " + nsStringToJuce (format.mediaType)); - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + #if JUCE_USE_NEW_CAMERA_API if (@available (iOS 10.0, *)) { String colourSpaces; @@ -592,12 +592,14 @@ private: captureOutput (createCaptureOutput()), photoOutputDelegate (nullptr) { + #if JUCE_USE_NEW_CAMERA_API if (@available (iOS 10.0, *)) { static PhotoOutputDelegateClass cls; photoOutputDelegate.reset ([cls.createInstance() init]); PhotoOutputDelegateClass::setOwner (photoOutputDelegate.get(), this); } + #endif captureSession.addOutputIfPossible (captureOutput); } @@ -617,9 +619,9 @@ private: if (auto* connection = findVideoConnection (captureOutput)) { - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + #if JUCE_USE_NEW_CAMERA_API if (@available (iOS 10.0, *)) - { + { if ([captureOutput isKindOfClass: [AVCapturePhotoOutput class]]) { auto* photoOutput = (AVCapturePhotoOutput*) captureOutput; @@ -669,7 +671,7 @@ private: private: static AVCaptureOutput* createCaptureOutput() { - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + #if JUCE_USE_NEW_CAMERA_API if (@available (iOS 10.0, *)) return [AVCapturePhotoOutput new]; #endif @@ -679,7 +681,7 @@ private: static void printImageOutputDebugInfo (AVCaptureOutput* captureOutput) { - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + #if JUCE_USE_NEW_CAMERA_API if (@available (iOS 10.0, *)) { if ([captureOutput isKindOfClass: [AVCapturePhotoOutput class]]) @@ -688,7 +690,7 @@ private: String typesString; - for (AVVideoCodecType type in photoOutput.availablePhotoCodecTypes) + for (id type in photoOutput.availablePhotoCodecTypes) typesString << nsStringToJuce (type) << " "; JUCE_CAMERA_LOG ("Available image codec types: " + typesString); @@ -741,7 +743,7 @@ private: String typesString; - for (AVVideoCodecType type in stillImageOutput.availableImageDataCodecTypes) + for (id type in stillImageOutput.availableImageDataCodecTypes) typesString << nsStringToJuce (type) << " "; JUCE_CAMERA_LOG ("Available image codec types: " + typesString); @@ -763,8 +765,7 @@ private: } //============================================================================== - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new") - + #if JUCE_USE_NEW_CAMERA_API class PhotoOutputDelegateClass : public ObjCClass { public: @@ -974,8 +975,7 @@ private: } } }; - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + #endif //============================================================================== void callListeners (const Image& image) @@ -1026,7 +1026,7 @@ private: void startRecording (const File& file, AVCaptureVideoOrientation orientationToUse) { - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + #if JUCE_USE_NEW_CAMERA_API if (@available (iOS 10.0, *)) printVideoOutputDebugInfo (movieFileOutput); #endif @@ -1058,7 +1058,7 @@ private: JUCE_CAMERA_LOG ("Available video codec types:"); #if JUCE_CAMERA_LOG_ENABLED - for (AVVideoCodecType type in output.availableVideoCodecTypes) + for (id type in output.availableVideoCodecTypes) JUCE_CAMERA_LOG (nsStringToJuce (type)); #endif @@ -1335,6 +1335,6 @@ String CameraDevice::getFileExtension() return ".mov"; } -#if JUCE_DEPRECATION_IGNORED +#if JUCE_USE_NEW_CAMERA_API JUCE_END_IGNORE_WARNINGS_GCC_LIKE #endif diff --git a/modules/juce_video/native/juce_mac_CameraDevice.h b/modules/juce_video/native/juce_mac_CameraDevice.h index cec2929da1..23b4892ef0 100644 --- a/modules/juce_video/native/juce_mac_CameraDevice.h +++ b/modules/juce_video/native/juce_mac_CameraDevice.h @@ -23,180 +23,12 @@ ============================================================================== */ +#if defined (MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15 + #define JUCE_USE_NEW_CAMERA_API 1 +#endif + struct CameraDevice::Pimpl { - #if defined (MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15 - #define JUCE_USE_NEW_APPLE_CAMERA_API 1 - #else - #define JUCE_USE_NEW_APPLE_CAMERA_API 0 - #endif - - #if JUCE_USE_NEW_APPLE_CAMERA_API - class PostCatalinaPhotoOutput - { - public: - PostCatalinaPhotoOutput() - { - static PhotoOutputDelegateClass cls; - delegate.reset ([cls.createInstance() init]); - } - - void addImageCapture (AVCaptureSession* s) - { - if (imageOutput != nil) - return; - - imageOutput = [[AVCapturePhotoOutput alloc] init]; - [s addOutput: imageOutput]; - } - - void removeImageCapture (AVCaptureSession* s) - { - if (imageOutput == nil) - return; - - [s removeOutput: imageOutput]; - [imageOutput release]; - imageOutput = nil; - } - - NSArray* getConnections() const - { - if (imageOutput != nil) - return imageOutput.connections; - - return nil; - } - - void triggerImageCapture (Pimpl& p) - { - if (imageOutput == nil) - return; - - PhotoOutputDelegateClass::setOwner (delegate.get(), &p); - - [imageOutput capturePhotoWithSettings: [AVCapturePhotoSettings photoSettings] - delegate: id (delegate.get())]; - } - - static NSArray* getAvailableDevices() - { - auto* discovery = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes: @[AVCaptureDeviceTypeBuiltInWideAngleCamera, - AVCaptureDeviceTypeExternalUnknown] - mediaType: AVMediaTypeVideo - position: AVCaptureDevicePositionUnspecified]; - return [discovery devices]; - } - - private: - class PhotoOutputDelegateClass : public ObjCClass - { - public: - PhotoOutputDelegateClass() : ObjCClass ("PhotoOutputDelegateClass_") - { - addMethod (@selector (captureOutput:didFinishProcessingPhoto:error:), didFinishProcessingPhoto, "v@:@@@"); - addIvar ("owner"); - registerClass(); - } - - static void didFinishProcessingPhoto (id self, SEL, AVCapturePhotoOutput*, AVCapturePhoto* photo, NSError* error) - { - if (error != nil) - { - String errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String(); - ignoreUnused (errorString); - - JUCE_CAMERA_LOG ("Still picture capture failed, error: " + errorString); - jassertfalse; - - return; - } - - auto* imageData = [photo fileDataRepresentation]; - auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); - - getOwner (self).imageCaptureFinished (image); - } - - static Pimpl& getOwner (id self) { return *getIvar (self, "owner"); } - static void setOwner (id self, Pimpl* t) { object_setInstanceVariable (self, "owner", t); } - }; - - AVCapturePhotoOutput* imageOutput = nil; - std::unique_ptr delegate; - }; - #else - struct PreCatalinaStillImageOutput - { - public: - void addImageCapture (AVCaptureSession* s) - { - if (imageOutput != nil) - return; - - const auto codecType = - #if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13 - AVVideoCodecTypeJPEG; - #else - AVVideoCodecJPEG; - #endif - - imageOutput = [[AVCaptureStillImageOutput alloc] init]; - auto imageSettings = [[NSDictionary alloc] initWithObjectsAndKeys: codecType, AVVideoCodecKey, nil]; - [imageOutput setOutputSettings: imageSettings]; - [imageSettings release]; - [s addOutput: imageOutput]; - } - - void removeImageCapture (AVCaptureSession* s) - { - if (imageOutput == nil) - return; - - [s removeOutput: imageOutput]; - [imageOutput release]; - imageOutput = nil; - } - - NSArray* getConnections() const - { - if (imageOutput != nil) - return imageOutput.connections; - - return nil; - } - - void triggerImageCapture (Pimpl& p) - { - if (auto* videoConnection = p.getVideoConnection()) - { - [imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection - completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError* error) - { - if (error != nil) - { - JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription)); - jassertfalse; - return; - } - - auto* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: sampleBuffer]; - auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); - p.imageCaptureFinished (image); - }]; - } - } - - static NSArray* getAvailableDevices() - { - return [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo]; - } - - private: - AVCaptureStillImageOutput* imageOutput = nil; - }; - #endif - Pimpl (CameraDevice& ownerToUse, const String& deviceNameToUse, int /*index*/, int /*minWidth*/, int /*minHeight*/, int /*maxWidth*/, int /*maxHeight*/, @@ -204,6 +36,16 @@ struct CameraDevice::Pimpl : owner (ownerToUse), deviceName (deviceNameToUse) { + imageOutput = []() -> std::unique_ptr + { + #if JUCE_USE_NEW_CAMERA_API + if (@available (macOS 10.15, *)) + return std::make_unique(); + #endif + + return std::make_unique(); + }(); + session = [[AVCaptureSession alloc] init]; session.sessionPreset = useHighQuality ? AVCaptureSessionPresetHigh @@ -299,13 +141,30 @@ struct CameraDevice::Pimpl listeners.remove (listenerToRemove); } - static StringArray getAvailableDevices() + static NSArray* getCaptureDevices() { - auto* devices = decltype (imageOutput)::getAvailableDevices(); + #if JUCE_USE_NEW_CAMERA_API + if (@available (macOS 10.15, *)) + { + auto* discovery = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes: @[AVCaptureDeviceTypeBuiltInWideAngleCamera, + AVCaptureDeviceTypeExternalUnknown] + mediaType: AVMediaTypeVideo + position: AVCaptureDevicePositionUnspecified]; + + return [discovery devices]; + } + #endif + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + return [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } + static StringArray getAvailableDevices() + { StringArray results; - for (AVCaptureDevice* device : devices) + for (AVCaptureDevice* device : getCaptureDevices()) results.add (nsStringToJuce ([device localizedName])); return results; @@ -373,10 +232,180 @@ private: } }; + //============================================================================== + struct ImageOutputBase + { + virtual ~ImageOutputBase() = default; + + virtual void addImageCapture (AVCaptureSession*) = 0; + virtual void removeImageCapture (AVCaptureSession*) = 0; + virtual NSArray* getConnections() const = 0; + virtual void triggerImageCapture (Pimpl& p) = 0; + }; + + #if JUCE_USE_NEW_CAMERA_API + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new") + class PostCatalinaPhotoOutput : public ImageOutputBase + { + public: + PostCatalinaPhotoOutput() + { + static PhotoOutputDelegateClass cls; + delegate.reset ([cls.createInstance() init]); + } + + void addImageCapture (AVCaptureSession* s) override + { + if (imageOutput != nil) + return; + + imageOutput = [[AVCapturePhotoOutput alloc] init]; + [s addOutput: imageOutput]; + } + + void removeImageCapture (AVCaptureSession* s) override + { + if (imageOutput == nil) + return; + + [s removeOutput: imageOutput]; + [imageOutput release]; + imageOutput = nil; + } + + NSArray* getConnections() const override + { + if (imageOutput != nil) + return imageOutput.connections; + + return nil; + } + + void triggerImageCapture (Pimpl& p) override + { + if (imageOutput == nil) + return; + + PhotoOutputDelegateClass::setOwner (delegate.get(), &p); + + [imageOutput capturePhotoWithSettings: [AVCapturePhotoSettings photoSettings] + delegate: id (delegate.get())]; + } + + private: + class PhotoOutputDelegateClass : public ObjCClass + { + public: + PhotoOutputDelegateClass() : ObjCClass ("PhotoOutputDelegateClass_") + { + addMethod (@selector (captureOutput:didFinishProcessingPhoto:error:), didFinishProcessingPhoto, "v@:@@@"); + addIvar ("owner"); + registerClass(); + } + + static void didFinishProcessingPhoto (id self, SEL, AVCapturePhotoOutput*, AVCapturePhoto* photo, NSError* error) + { + if (error != nil) + { + String errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String(); + ignoreUnused (errorString); + + JUCE_CAMERA_LOG ("Still picture capture failed, error: " + errorString); + jassertfalse; + + return; + } + + auto* imageData = [photo fileDataRepresentation]; + auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); + + getOwner (self).imageCaptureFinished (image); + } + + static Pimpl& getOwner (id self) { return *getIvar (self, "owner"); } + static void setOwner (id self, Pimpl* t) { object_setInstanceVariable (self, "owner", t); } + }; + + AVCapturePhotoOutput* imageOutput = nil; + std::unique_ptr delegate; + }; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + #endif + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + class PreCatalinaStillImageOutput : public ImageOutputBase + { + public: + void addImageCapture (AVCaptureSession* s) override + { + if (imageOutput != nil) + return; + + const auto codecType = [] + { + #if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 + if (@available (macOS 10.13, *)) + return AVVideoCodecTypeJPEG; + #endif + + return AVVideoCodecJPEG; + }(); + + imageOutput = [[AVCaptureStillImageOutput alloc] init]; + auto imageSettings = [[NSDictionary alloc] initWithObjectsAndKeys: codecType, AVVideoCodecKey, nil]; + [imageOutput setOutputSettings: imageSettings]; + [imageSettings release]; + [s addOutput: imageOutput]; + } + + void removeImageCapture (AVCaptureSession* s) override + { + if (imageOutput == nil) + return; + + [s removeOutput: imageOutput]; + [imageOutput release]; + imageOutput = nil; + } + + NSArray* getConnections() const override + { + if (imageOutput != nil) + return imageOutput.connections; + + return nil; + } + + void triggerImageCapture (Pimpl& p) override + { + if (auto* videoConnection = p.getVideoConnection()) + { + [imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection + completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError* error) + { + if (error != nil) + { + JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription)); + jassertfalse; + return; + } + + auto* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: sampleBuffer]; + auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); + p.imageCaptureFinished (image); + }]; + } + } + + private: + AVCaptureStillImageOutput* imageOutput = nil; + }; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + //============================================================================== void addImageCapture() { - imageOutput.addImageCapture (session); + imageOutput->addImageCapture (session); } void addMovieCapture() @@ -390,7 +419,7 @@ private: void removeImageCapture() { - imageOutput.removeImageCapture (session); + imageOutput->removeImageCapture (session); } void removeMovieCapture() @@ -419,9 +448,7 @@ private: { if (currentInput == nil) { - auto* availableDevices = decltype (imageOutput)::getAvailableDevices(); - - for (AVCaptureDevice* device : availableDevices) + for (AVCaptureDevice* device : getCaptureDevices()) { if (deviceName == nsStringToJuce ([device localizedName])) { @@ -480,7 +507,7 @@ private: AVCaptureConnection* getVideoConnection() const { - auto* connections = imageOutput.getConnections(); + auto* connections = imageOutput->getConnections(); if (connections != nil) for (AVCaptureConnection* connection in connections) @@ -519,7 +546,7 @@ private: startSession(); if (auto* videoConnection = getVideoConnection()) - imageOutput.triggerImageCapture (*this); + imageOutput->triggerImageCapture (*this); } void cameraSessionRuntimeError (const String& error) @@ -536,11 +563,7 @@ private: AVCaptureSession* session = nil; AVCaptureMovieFileOutput* fileOutput = nil; - #if JUCE_USE_NEW_APPLE_CAMERA_API - PostCatalinaPhotoOutput imageOutput; - #else - PreCatalinaStillImageOutput imageOutput; - #endif + std::unique_ptr imageOutput; AVCaptureDeviceInput* currentInput = nil; id callbackDelegate = nil; @@ -578,5 +601,3 @@ String CameraDevice::getFileExtension() { return ".mov"; } - -#undef JUCE_USE_NEW_APPLE_CAMERA_API