| @@ -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 | |||
| } | |||
| } | |||
| @@ -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<ImplementationStrategy::onlyNew> : 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<ImplementationStrategy::onlyNew> | |||
| @@ -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<AUAudioUnitViewConfiguration*>*) = 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<AUAudioUnitViewConfiguration*>* 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<AUAudioUnitViewConfiguration*>* 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 | |||
| @@ -39,18 +39,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
| #include <AudioUnit/AudioUnitCarbonView.h> | |||
| #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 <CoreAudioKit/AUViewController.h> | |||
| #endif | |||
| #include <CoreAudioKit/AUViewController.h> | |||
| #include <juce_audio_basics/native/juce_mac_CoreAudioLayouts.h> | |||
| #include <juce_audio_basics/midi/juce_MidiDataConcatenator.h> | |||
| @@ -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<int> (size.width), static_cast<int> (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<ViewControllerCallbackBlock> 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<AUCompletionCallbackBlock> 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)); | |||
| } | |||
| } | |||
| @@ -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<NSObject> | |||
| { | |||
| @@ -159,20 +157,12 @@ private: | |||
| bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback, | |||
| Rectangle<int>* 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<int>*) | |||
| { | |||
| std::unique_ptr<ModalComponentManager::Callback> 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 | |||
| @@ -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 | |||
| @@ -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()) | |||
| @@ -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")]); | |||
| @@ -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 | |||
| //============================================================================== | |||
| @@ -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::Pimpl> FileChooser::showPlatformDialog (FileChooser | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| #endif | |||
| #undef JUCE_DEPRECATION_IGNORED | |||
| } // namespace juce | |||
| @@ -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 | |||
| @@ -787,7 +787,7 @@ static Rectangle<int> getRecommendedWindowBounds() | |||
| static BorderSize<int> 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<int> 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 }; | |||
| @@ -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 <NSDraggingInfo> sender) | |||
| @@ -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) | |||
| @@ -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); | |||
| @@ -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<WKWebView>; | |||
| #else | |||
| using WebViewBase = ObjCClass<WebView>; | |||
| #endif | |||
| struct WebViewKeyEquivalentResponder : public WebViewBase | |||
| template <class WebViewClass> | |||
| struct WebViewKeyEquivalentResponder : public ObjCClass<WebViewClass> | |||
| { | |||
| WebViewKeyEquivalentResponder() | |||
| : WebViewBase ("WebViewKeyEquivalentResponder_") | |||
| : ObjCClass<WebViewClass> ("WebViewKeyEquivalentResponder_") | |||
| { | |||
| addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@"); | |||
| registerClass(); | |||
| ObjCClass<WebViewClass>::addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@"); | |||
| ObjCClass<WebViewClass>::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<BOOL> (self, selector, event); | |||
| return ObjCClass<WebViewClass>::template sendSuperclassMessage<BOOL> (self, selector, event); | |||
| } | |||
| }; | |||
| #endif | |||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
| struct DownloadClickDetectorClass : public ObjCClass<NSObject> | |||
| { | |||
| DownloadClickDetectorClass() : ObjCClass<NSObject> ("JUCEWebClickDetector_") | |||
| { | |||
| addIvar<WebBrowserComponent*> ("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<WebBrowserComponent*> (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<WebPolicyDecisionListener> listener) | |||
| { | |||
| if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation))) | |||
| [listener use]; | |||
| else | |||
| [listener ignore]; | |||
| } | |||
| static void decidePolicyForNewWindowAction (id self, SEL, WebView*, NSDictionary* actionInformation, | |||
| NSURLRequest*, NSString*, id<WebPolicyDecisionListener> 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<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles) | |||
| { | |||
| struct DeletedFileChooserWrapper : private DeletedAtShutdown | |||
| { | |||
| DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, id<WebOpenPanelResultListener> rl) | |||
| : chooser (std::move (fc)), listener (rl) | |||
| { | |||
| [listener retain]; | |||
| } | |||
| ~DeletedFileChooserWrapper() | |||
| { | |||
| [listener release]; | |||
| } | |||
| std::unique_ptr<FileChooser> chooser; | |||
| id<WebOpenPanelResultListener> listener; | |||
| }; | |||
| auto chooser = std::make_unique<FileChooser> (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<NSObject> | |||
| { | |||
| @@ -182,7 +285,8 @@ struct WebViewDelegateClass : public ObjCClass<NSObject> | |||
| 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<NSURL*>*)) | |||
| { | |||
| @@ -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<WebView> 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<NSObject> | |||
| { | |||
| DownloadClickDetectorClass() : ObjCClass<NSObject> ("JUCEWebClickDetector_") | |||
| { | |||
| addIvar<WebBrowserComponent*> ("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<WebBrowserComponent*> (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<WebPolicyDecisionListener> 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<WKWebView> webviewClass; | |||
| webView = (WKWebView*) webviewClass.createInstance(); | |||
| static void decidePolicyForNewWindowAction (id self, SEL, WebView*, NSDictionary* actionInformation, | |||
| NSURLRequest*, NSString*, id<WebPolicyDecisionListener> 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<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles) | |||
| { | |||
| struct DeletedFileChooserWrapper : private DeletedAtShutdown | |||
| if (trimmed.startsWithIgnoreCase ("javascript:")) | |||
| { | |||
| DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, id<WebOpenPanelResultListener> rl) | |||
| : chooser (std::move (fc)), listener (rl) | |||
| { | |||
| [listener retain]; | |||
| } | |||
| ~DeletedFileChooserWrapper() | |||
| { | |||
| [listener release]; | |||
| } | |||
| std::unique_ptr<FileChooser> chooser; | |||
| id<WebOpenPanelResultListener> listener; | |||
| }; | |||
| [webView evaluateJavaScript: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false)) | |||
| completionHandler: nil]; | |||
| auto chooser = std::make_unique<FileChooser> (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<NSObject> | |||
| { | |||
| WebViewDelegateClass() : ObjCClass<NSObject> ("JUCEWebViewDelegate_") | |||
| { | |||
| addIvar<WebBrowserComponent*> ("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<WebBrowserComponent*> (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<WKWebViewImpl> (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<WebViewImpl> (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<WebViewBase> webView; | |||
| }; | |||
| #endif | |||
| //============================================================================== | |||
| WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden) | |||
| : unloadPageWhenHidden (unloadWhenHidden) | |||
| @@ -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<int> (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<ReferenceCountedObject> viewAttachment; | |||
| @@ -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" | |||
| @@ -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<AVCaptureDevice*>* 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<NSMutableArray<AVCaptureDeviceType>, 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<NSObject> | |||
| { | |||
| 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 | |||
| @@ -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<AVCaptureConnection*>* 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<AVCapturePhotoCaptureDelegate> (delegate.get())]; | |||
| } | |||
| static NSArray* getAvailableDevices() | |||
| { | |||
| auto* discovery = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes: @[AVCaptureDeviceTypeBuiltInWideAngleCamera, | |||
| AVCaptureDeviceTypeExternalUnknown] | |||
| mediaType: AVMediaTypeVideo | |||
| position: AVCaptureDevicePositionUnspecified]; | |||
| return [discovery devices]; | |||
| } | |||
| private: | |||
| class PhotoOutputDelegateClass : public ObjCClass<NSObject> | |||
| { | |||
| public: | |||
| PhotoOutputDelegateClass() : ObjCClass<NSObject> ("PhotoOutputDelegateClass_") | |||
| { | |||
| addMethod (@selector (captureOutput:didFinishProcessingPhoto:error:), didFinishProcessingPhoto, "v@:@@@"); | |||
| addIvar<Pimpl*> ("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<Pimpl*> (self, "owner"); } | |||
| static void setOwner (id self, Pimpl* t) { object_setInstanceVariable (self, "owner", t); } | |||
| }; | |||
| AVCapturePhotoOutput* imageOutput = nil; | |||
| std::unique_ptr<NSObject, NSObjectDeleter> 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<AVCaptureConnection*>* 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<ImageOutputBase> | |||
| { | |||
| #if JUCE_USE_NEW_CAMERA_API | |||
| if (@available (macOS 10.15, *)) | |||
| return std::make_unique<PostCatalinaPhotoOutput>(); | |||
| #endif | |||
| return std::make_unique<PreCatalinaStillImageOutput>(); | |||
| }(); | |||
| 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<AVCaptureConnection*>* 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<AVCaptureConnection*>* 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<AVCapturePhotoCaptureDelegate> (delegate.get())]; | |||
| } | |||
| private: | |||
| class PhotoOutputDelegateClass : public ObjCClass<NSObject> | |||
| { | |||
| public: | |||
| PhotoOutputDelegateClass() : ObjCClass<NSObject> ("PhotoOutputDelegateClass_") | |||
| { | |||
| addMethod (@selector (captureOutput:didFinishProcessingPhoto:error:), didFinishProcessingPhoto, "v@:@@@"); | |||
| addIvar<Pimpl*> ("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<Pimpl*> (self, "owner"); } | |||
| static void setOwner (id self, Pimpl* t) { object_setInstanceVariable (self, "owner", t); } | |||
| }; | |||
| AVCapturePhotoOutput* imageOutput = nil; | |||
| std::unique_ptr<NSObject, NSObjectDeleter> 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<AVCaptureConnection*>* 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<ImageOutputBase> imageOutput; | |||
| AVCaptureDeviceInput* currentInput = nil; | |||
| id<AVCaptureFileOutputRecordingDelegate> callbackDelegate = nil; | |||
| @@ -578,5 +601,3 @@ String CameraDevice::getFileExtension() | |||
| { | |||
| return ".mov"; | |||
| } | |||
| #undef JUCE_USE_NEW_APPLE_CAMERA_API | |||