From 5d5fdaf0083b70cd48aa7553f90908edcc176514 Mon Sep 17 00:00:00 2001 From: ed Date: Tue, 21 Dec 2021 10:00:51 +0000 Subject: [PATCH 001/557] Projucer: Fix relative paths for Android resource files --- .../Source/ProjectSaving/jucer_ProjectExport_Android.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h index 65d56b87fc..b36320e81d 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h @@ -244,7 +244,7 @@ public: if (androidExtraAssetsFolderValue.isNotEmpty()) { - auto extraAssets = getProject().getFile().getParentDirectory().getChildFile (androidExtraAssetsFolderValue); + auto extraAssets = getProject().getFile().getSiblingFile (androidExtraAssetsFolderValue); if (extraAssets.exists() && extraAssets.isDirectory()) { @@ -1284,7 +1284,7 @@ private: if (remoteNotifsConfigFilePath.isEmpty()) remoteNotifsConfigFilePath = androidRemoteNotificationsConfigFile.get().toString(); - File file (getProject().getFile().getChildFile (remoteNotifsConfigFilePath)); + File file (getProject().getFile().getSiblingFile (remoteNotifsConfigFilePath)); // Settings file must be present for remote notifications to work and it must be called google-services.json. jassert (file.existsAsFile() && file.getFileName() == "google-services.json"); @@ -1303,7 +1303,7 @@ private: for (auto& path : resourcePaths) { - auto file = getProject().getFile().getChildFile (path); + auto file = getProject().getFile().getSiblingFile (path); jassert (file.exists()); From 5737c42ccf0a5c29abea4f650c34b53f8ae37c32 Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Wed, 20 Nov 2024 10:10:35 +0000 Subject: [PATCH 002/557] Use getSiblingFile in more places --- examples/Assets/DemoUtilities.h | 6 +++--- .../Builds/Android/app/src/main/assets/DemoUtilities.h | 6 +++--- examples/DemoRunner/Source/Demos/JUCEDemos.cpp | 4 ++-- .../Builds/Android/app/src/main/assets/DemoUtilities.h | 6 +++--- .../Projucer/Source/Utility/PIPs/jucer_PIPGenerator.cpp | 2 +- modules/juce_core/files/juce_File.cpp | 8 ++++---- modules/juce_gui_basics/drawables/juce_SVGParser.cpp | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/Assets/DemoUtilities.h b/examples/Assets/DemoUtilities.h index 1c5534bff7..4f1f0f2631 100644 --- a/examples/Assets/DemoUtilities.h +++ b/examples/Assets/DemoUtilities.h @@ -63,7 +63,7 @@ inline File getExamplesDirectory() noexcept return File { CharPointer_UTF8 { PIP_JUCE_EXAMPLES_DIRECTORY_STRING } }; #else auto currentFile = File::getSpecialLocation (File::SpecialLocationType::currentApplicationFile); - auto exampleDir = currentFile.getParentDirectory().getChildFile ("examples"); + auto exampleDir = currentFile.getSiblingFile ("examples"); if (exampleDir.exists()) return exampleDir; @@ -109,10 +109,10 @@ inline std::unique_ptr createAssetInputStream (const char* resource #else #if JUCE_IOS auto assetsDir = File::getSpecialLocation (File::currentExecutableFile) - .getParentDirectory().getChildFile ("Assets"); + .getSiblingFile ("Assets"); #elif JUCE_MAC auto assetsDir = File::getSpecialLocation (File::currentExecutableFile) - .getParentDirectory().getParentDirectory().getChildFile ("Resources").getChildFile ("Assets"); + .getParentDirectory().getSiblingFile ("Resources").getChildFile ("Assets"); if (! assetsDir.exists()) assetsDir = getExamplesDirectory().getChildFile ("Assets"); diff --git a/examples/DemoRunner/Builds/Android/app/src/main/assets/DemoUtilities.h b/examples/DemoRunner/Builds/Android/app/src/main/assets/DemoUtilities.h index 1c5534bff7..4f1f0f2631 100644 --- a/examples/DemoRunner/Builds/Android/app/src/main/assets/DemoUtilities.h +++ b/examples/DemoRunner/Builds/Android/app/src/main/assets/DemoUtilities.h @@ -63,7 +63,7 @@ inline File getExamplesDirectory() noexcept return File { CharPointer_UTF8 { PIP_JUCE_EXAMPLES_DIRECTORY_STRING } }; #else auto currentFile = File::getSpecialLocation (File::SpecialLocationType::currentApplicationFile); - auto exampleDir = currentFile.getParentDirectory().getChildFile ("examples"); + auto exampleDir = currentFile.getSiblingFile ("examples"); if (exampleDir.exists()) return exampleDir; @@ -109,10 +109,10 @@ inline std::unique_ptr createAssetInputStream (const char* resource #else #if JUCE_IOS auto assetsDir = File::getSpecialLocation (File::currentExecutableFile) - .getParentDirectory().getChildFile ("Assets"); + .getSiblingFile ("Assets"); #elif JUCE_MAC auto assetsDir = File::getSpecialLocation (File::currentExecutableFile) - .getParentDirectory().getParentDirectory().getChildFile ("Resources").getChildFile ("Assets"); + .getParentDirectory().getSiblingFile ("Resources").getChildFile ("Assets"); if (! assetsDir.exists()) assetsDir = getExamplesDirectory().getChildFile ("Assets"); diff --git a/examples/DemoRunner/Source/Demos/JUCEDemos.cpp b/examples/DemoRunner/Source/Demos/JUCEDemos.cpp index e6b42fad4d..506c27da8c 100644 --- a/examples/DemoRunner/Source/Demos/JUCEDemos.cpp +++ b/examples/DemoRunner/Source/Demos/JUCEDemos.cpp @@ -50,7 +50,7 @@ void JUCEDemos::registerDemo (std::function constructorCallback, c { #if JUCE_MAC auto f = File::getSpecialLocation (File::currentExecutableFile) - .getParentDirectory().getParentDirectory().getChildFile ("Resources"); + .getParentDirectory().getSiblingFile ("Resources"); #else auto f = findExamplesDirectoryFromExecutable (File::getSpecialLocation (File::currentApplicationFile)); #endif @@ -69,7 +69,7 @@ void JUCEDemos::registerDemo (std::function constructorCallback, c File JUCEDemos::findExamplesDirectoryFromExecutable (File exec) { int numTries = 15; - auto exampleDir = exec.getParentDirectory().getChildFile ("examples"); + auto exampleDir = exec.getSiblingFile ("examples"); if (exampleDir.exists()) return exampleDir; diff --git a/extras/AudioPluginHost/Builds/Android/app/src/main/assets/DemoUtilities.h b/extras/AudioPluginHost/Builds/Android/app/src/main/assets/DemoUtilities.h index 1c5534bff7..4f1f0f2631 100644 --- a/extras/AudioPluginHost/Builds/Android/app/src/main/assets/DemoUtilities.h +++ b/extras/AudioPluginHost/Builds/Android/app/src/main/assets/DemoUtilities.h @@ -63,7 +63,7 @@ inline File getExamplesDirectory() noexcept return File { CharPointer_UTF8 { PIP_JUCE_EXAMPLES_DIRECTORY_STRING } }; #else auto currentFile = File::getSpecialLocation (File::SpecialLocationType::currentApplicationFile); - auto exampleDir = currentFile.getParentDirectory().getChildFile ("examples"); + auto exampleDir = currentFile.getSiblingFile ("examples"); if (exampleDir.exists()) return exampleDir; @@ -109,10 +109,10 @@ inline std::unique_ptr createAssetInputStream (const char* resource #else #if JUCE_IOS auto assetsDir = File::getSpecialLocation (File::currentExecutableFile) - .getParentDirectory().getChildFile ("Assets"); + .getSiblingFile ("Assets"); #elif JUCE_MAC auto assetsDir = File::getSpecialLocation (File::currentExecutableFile) - .getParentDirectory().getParentDirectory().getChildFile ("Resources").getChildFile ("Assets"); + .getParentDirectory().getSiblingFile ("Resources").getChildFile ("Assets"); if (! assetsDir.exists()) assetsDir = getExamplesDirectory().getChildFile ("Assets"); diff --git a/extras/Projucer/Source/Utility/PIPs/jucer_PIPGenerator.cpp b/extras/Projucer/Source/Utility/PIPs/jucer_PIPGenerator.cpp index 11f7698cae..4e66e8afed 100644 --- a/extras/Projucer/Source/Utility/PIPs/jucer_PIPGenerator.cpp +++ b/extras/Projucer/Source/Utility/PIPs/jucer_PIPGenerator.cpp @@ -507,7 +507,7 @@ Array PIPGenerator::replaceRelativeIncludesAndGetFilesToMove() if (path.startsWith ("<") && path.endsWith (">")) continue; - auto file = pipFile.getParentDirectory().getChildFile (path); + auto file = pipFile.getSiblingFile (path); files.add (file); line = line.replace (path, file.getFileName()); diff --git a/modules/juce_core/files/juce_File.cpp b/modules/juce_core/files/juce_File.cpp index 073ee8e925..ebc5a95796 100644 --- a/modules/juce_core/files/juce_File.cpp +++ b/modules/juce_core/files/juce_File.cpp @@ -1158,10 +1158,10 @@ public: expect (home.getChildFile ("...xyz").getFileName() == "...xyz"); expect (home.getChildFile ("./xyz") == home.getChildFile ("xyz")); expect (home.getChildFile ("././xyz") == home.getChildFile ("xyz")); - expect (home.getChildFile ("../xyz") == home.getParentDirectory().getChildFile ("xyz")); - expect (home.getChildFile (".././xyz") == home.getParentDirectory().getChildFile ("xyz")); - expect (home.getChildFile (".././xyz/./abc") == home.getParentDirectory().getChildFile ("xyz/abc")); - expect (home.getChildFile ("./../xyz") == home.getParentDirectory().getChildFile ("xyz")); + expect (home.getChildFile ("../xyz") == home.getSiblingFile ("xyz")); + expect (home.getChildFile (".././xyz") == home.getSiblingFile ("xyz")); + expect (home.getChildFile (".././xyz/./abc") == home.getSiblingFile ("xyz/abc")); + expect (home.getChildFile ("./../xyz") == home.getSiblingFile ("xyz")); expect (home.getChildFile ("a1/a2/a3/./../../a4") == home.getChildFile ("a1/a4")); expect (! File().hasReadAccess()); diff --git a/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/modules/juce_gui_basics/drawables/juce_SVGParser.cpp index 04175c6e5c..56ce6b2bbd 100644 --- a/modules/juce_gui_basics/drawables/juce_SVGParser.cpp +++ b/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -1299,7 +1299,7 @@ private: } else { - auto linkedFile = originalFile.getParentDirectory().getChildFile (link); + auto linkedFile = originalFile.getSiblingFile (link); if (linkedFile.existsAsFile()) inputStream = linkedFile.createInputStream(); From 01bfa988277dd3968ce352c5ba54492879ea0945 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 18 Nov 2024 12:18:24 +0000 Subject: [PATCH 003/557] CoreGraphics: Fix incorrect behaviour of non-solid-colour text fills Previously, filling a string containing a space or other non-rendered character with a gradient would end up filling the entire clip region. The correct behaviour is to completely skip filling any empty paths. --- modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm index 1caaf7e7b2..aa18f90ac1 100644 --- a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm +++ b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm @@ -867,6 +867,10 @@ void CoreGraphicsContext::drawGlyphs (Span glyphs, Path p; auto& f = state->font; f.getTypefacePtr()->getOutlineForGlyph (f.getMetricsKind(), glyph, p); + + if (p.isEmpty()) + continue; + const auto scale = f.getHeight(); fillPath (p, AffineTransform::scale (scale * f.getHorizontalScale(), scale).translated (positions[index]).followedBy (transform)); } From 0aaaea265a1015ec76c6bedb38476063ffb237e6 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 20 Nov 2024 20:21:51 +0000 Subject: [PATCH 004/557] AU Client: Ignore availability warnings for MIDIEventList functions Xcode 13.2.1 warns on these functions, despite the lambdas being declared inside an @availability-checked block. --- .../juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm index 8e1375263b..7057a0f7f9 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm @@ -2193,7 +2193,9 @@ private: const auto init = [&] { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new") end = MIDIEventListInit (&stackList, kMIDIProtocol_1_0); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE }; const auto send = [&] @@ -2206,6 +2208,7 @@ private: static_assert (sizeof (uint32_t) == sizeof (UInt32) && alignof (uint32_t) == alignof (UInt32), "If this fails, the cast below will be broken too!"); + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new") using List = struct MIDIEventList; end = MIDIEventListAdd (&stackList, sizeof (List::packet), @@ -2213,6 +2216,7 @@ private: (MIDITimeStamp) timeStamp, view.size(), reinterpret_cast (view.data())); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE }; init(); From 4e6440c3d7e7ceb9d49c9e4f9347cde609308603 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 18 Nov 2024 13:59:32 +0000 Subject: [PATCH 005/557] AAX Client: Remove channel layout compatibility checks It's important that the plugin always returns the full set of available components. The plugin may be scanned by a separate process from the 'main' DAW process, and these processes may report different compatibility levels. If the scanner has more restricted compatibility than the 'main' DAW process, then some channel layouts may not be registered, and will be hidden in the DAW. --- .../juce_audio_plugin_client_AAX.cpp | 44 ++++--------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp index 416423fd15..abeba58e65 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp @@ -2546,30 +2546,6 @@ namespace AAXClasses check (desc.AddProcessProc_Native (algorithmProcessCallback, properties)); } - static inline bool hostSupportsStemFormat (AAX_EStemFormat stemFormat, const AAX_IFeatureInfo* featureInfo) - { - if (featureInfo != nullptr) - { - AAX_ESupportLevel supportLevel; - - if (featureInfo->SupportLevel (supportLevel) == AAX_SUCCESS && supportLevel == AAX_eSupportLevel_ByProperty) - { - std::unique_ptr props (featureInfo->AcquireProperties()); - - // Due to a bug in ProTools 12.8, ProTools thinks that AAX_eStemFormat_Ambi_1_ACN is not supported - // To workaround this bug, check if ProTools supports AAX_eStemFormat_Ambi_2_ACN, and, if yes, - // we can safely assume that it will also support AAX_eStemFormat_Ambi_1_ACN - if (stemFormat == AAX_eStemFormat_Ambi_1_ACN) - stemFormat = AAX_eStemFormat_Ambi_2_ACN; - - if (props != nullptr && props->GetProperty ((AAX_EProperty) stemFormat, (AAX_CPropertyValue*) &supportLevel) != 0) - return (supportLevel == AAX_eSupportLevel_Supported); - } - } - - return (AAX_STEM_FORMAT_INDEX (stemFormat) <= 12); - } - static void getPlugInDescription (AAX_IEffectDescriptor& descriptor, [[maybe_unused]] const AAX_IFeatureInfo* featureInfo) { auto plugin = createPluginFilterOfType (AudioProcessor::wrapperType_AAX); @@ -2628,19 +2604,15 @@ namespace AAXClasses auto aaxOutFormat = numOuts > 0 ? aaxFormats[outIdx] : AAX_eStemFormat_None; auto outLayout = channelSetFromStemFormat (aaxOutFormat, false); - if (hostSupportsStemFormat (aaxInFormat, featureInfo) - && hostSupportsStemFormat (aaxOutFormat, featureInfo)) + AudioProcessor::BusesLayout fullLayout; + + if (! JuceAAX_Processor::fullBusesLayoutFromMainLayout (*plugin, inLayout, outLayout, fullLayout)) + continue; + + if (auto* desc = descriptor.NewComponentDescriptor()) { - AudioProcessor::BusesLayout fullLayout; - - if (! JuceAAX_Processor::fullBusesLayoutFromMainLayout (*plugin, inLayout, outLayout, fullLayout)) - continue; - - if (auto* desc = descriptor.NewComponentDescriptor()) - { - createDescriptor (*desc, fullLayout, *plugin, pluginIds, numMeters); - check (descriptor.AddComponent (desc)); - } + createDescriptor (*desc, fullLayout, *plugin, pluginIds, numMeters); + check (descriptor.AddComponent (desc)); } } } From 04fa895f385ad6de7fe1838bfb1d88922412f1f7 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 21 Nov 2024 13:26:44 +0000 Subject: [PATCH 006/557] AAX Client: Enable host-provided editor when plugin does not supply an editor --- .../juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp index abeba58e65..ade6f87bd9 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp @@ -2466,6 +2466,9 @@ namespace AAXClasses properties->AddProperty (AAX_eProperty_InputStemFormat, static_cast (aaxInputFormat)); properties->AddProperty (AAX_eProperty_OutputStemFormat, static_cast (aaxOutputFormat)); + // If the plugin doesn't have an editor, ask the host to provide one + properties->AddProperty (AAX_eProperty_UsesClientGUI, static_cast (! processor.hasEditor())); + const auto& extensions = processor.getAAXClientExtensions(); // This value needs to match the RTAS wrapper's Type ID, so that From 6e910d801050e017a590a7ac2d0d2f3ea0bd187f Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 18 Nov 2024 13:32:00 +0000 Subject: [PATCH 007/557] VST2 Client: Avoid C-style casts of function pointers --- .../juce_audio_plugin_client_VST2.cpp | 87 ++++++++++--------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp index f3702c6c95..629185e1b6 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp @@ -274,10 +274,38 @@ public: memset (&vstEffect, 0, sizeof (vstEffect)); vstEffect.magic = 0x56737450 /* 'VstP' */; - vstEffect.dispatcher = (Vst2::AEffectDispatcherProc) dispatcherCB; + vstEffect.dispatcher = [] (Vst2::AEffect* vstInterface, + Vst2::VstInt32 opCode, + Vst2::VstInt32 index, + Vst2::VstIntPtr value, + void* ptr, + float opt) -> Vst2::VstIntPtr + { + auto* wrapper = getWrapper (vstInterface); + VstOpCodeArguments args = { index, value, ptr, opt }; + + if (opCode == Vst2::effClose) + { + wrapper->dispatcher (opCode, args); + delete wrapper; + return 1; + } + + return wrapper->dispatcher (opCode, args); + }; + vstEffect.process = nullptr; - vstEffect.setParameter = (Vst2::AEffectSetParameterProc) setParameterCB; - vstEffect.getParameter = (Vst2::AEffectGetParameterProc) getParameterCB; + + vstEffect.setParameter = [] (Vst2::AEffect* vstInterface, Vst2::VstInt32 index, float value) + { + getWrapper (vstInterface)->setParameter (index, value); + }; + + vstEffect.getParameter = [] (Vst2::AEffect* vstInterface, Vst2::VstInt32 index) -> float + { + return getWrapper (vstInterface)->getParameter (index); + }; + vstEffect.numPrograms = jmax (1, processor->getNumPrograms()); vstEffect.numParams = juceParameters.getNumParameters(); vstEffect.numInputs = maxNumInChannels; @@ -292,8 +320,21 @@ public: vstEffect.version = JucePlugin_VersionCode; #endif - vstEffect.processReplacing = (Vst2::AEffectProcessProc) processReplacingCB; - vstEffect.processDoubleReplacing = (Vst2::AEffectProcessDoubleProc) processDoubleReplacingCB; + vstEffect.processReplacing = [] (Vst2::AEffect* vstInterface, + float** inputs, + float** outputs, + Vst2::VstInt32 sampleFrames) + { + getWrapper (vstInterface)->processReplacing (inputs, outputs, sampleFrames); + }; + + vstEffect.processDoubleReplacing = [] (Vst2::AEffect* vstInterface, + double** inputs, + double** outputs, + Vst2::VstInt32 sampleFrames) + { + getWrapper (vstInterface)->processDoubleReplacing (inputs, outputs, sampleFrames); + }; vstEffect.flags |= Vst2::effFlagsHasEditor; @@ -505,22 +546,12 @@ public: internalProcessReplacing (inputs, outputs, sampleFrames, floatTempBuffers); } - static void processReplacingCB (Vst2::AEffect* vstInterface, float** inputs, float** outputs, int32 sampleFrames) - { - getWrapper (vstInterface)->processReplacing (inputs, outputs, sampleFrames); - } - void processDoubleReplacing (double** inputs, double** outputs, int32 sampleFrames) { jassert (processor->isUsingDoublePrecision()); internalProcessReplacing (inputs, outputs, sampleFrames, doubleTempBuffers); } - static void processDoubleReplacingCB (Vst2::AEffect* vstInterface, double** inputs, double** outputs, int32 sampleFrames) - { - getWrapper (vstInterface)->processDoubleReplacing (inputs, outputs, sampleFrames); - } - //============================================================================== void resume() { @@ -678,22 +709,12 @@ public: return 0.0f; } - static float getParameterCB (Vst2::AEffect* vstInterface, int32 index) - { - return getWrapper (vstInterface)->getParameter (index); - } - void setParameter (int32 index, float value) { if (auto* param = juceParameters.getParamForIndex (index)) setValueAndNotifyIfChanged (*param, value); } - static void setParameterCB (Vst2::AEffect* vstInterface, int32 index, float value) - { - getWrapper (vstInterface)->setParameter (index, value); - } - void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override { if (inParameterChangedCallback.get()) @@ -902,22 +923,6 @@ public: } } - static pointer_sized_int dispatcherCB (Vst2::AEffect* vstInterface, int32 opCode, int32 index, - pointer_sized_int value, void* ptr, float opt) - { - auto* wrapper = getWrapper (vstInterface); - VstOpCodeArguments args = { index, value, ptr, opt }; - - if (opCode == Vst2::effClose) - { - wrapper->dispatcher (opCode, args); - delete wrapper; - return 1; - } - - return wrapper->dispatcher (opCode, args); - } - //============================================================================== // A component to hold the AudioProcessorEditor, and cope with some housekeeping // chores when it changes or repaints. From 73cb3b88adbce84abd30ed2bef765278241d2ca1 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 18 Nov 2024 13:32:07 +0000 Subject: [PATCH 008/557] VST2 Host: Avoid C-style casts of function pointers --- .../format_types/juce_VSTPluginFormat.cpp | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index e1c214c3cc..08b48f1f32 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -81,7 +81,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) #endif #ifndef JUCE_VST_WRAPPER_INVOKE_MAIN -#define JUCE_VST_WRAPPER_INVOKE_MAIN effect = module->moduleMain ((Vst2::audioMasterCallback) &audioMaster); +#define JUCE_VST_WRAPPER_INVOKE_MAIN effect = module->moduleMain (audioMaster); #endif #ifndef JUCE_VST_FALLBACK_HOST_NAME @@ -222,8 +222,7 @@ namespace } //============================================================================== -typedef Vst2::AEffect* (VSTCALLBACK *MainCall) (Vst2::audioMasterCallback); -static pointer_sized_int VSTCALLBACK audioMaster (Vst2::AEffect*, int32, int32, pointer_sized_int, void*, float); +using MainCall = Vst2::AEffect* (VSTCALLBACK*) (Vst2::audioMasterCallback); //============================================================================== // Change this to disable logging of various VST activities @@ -2156,6 +2155,20 @@ private: UseResFile (module->resFileId); #endif + constexpr Vst2::audioMasterCallback audioMaster = [] (Vst2::AEffect* eff, + Vst2::VstInt32 opcode, + Vst2::VstInt32 index, + Vst2::VstIntPtr value, + void* ptr, + float opt) -> Vst2::VstIntPtr + { + if (eff != nullptr) + if (auto* instance = (VSTPluginInstance*) (eff->resvd2)) + return instance->handleCallback (opcode, index, value, ptr, opt); + + return VSTPluginInstance::handleGeneralCallback (opcode, index, value, ptr, opt); + }; + { JUCE_VST_WRAPPER_INVOKE_MAIN } @@ -3458,15 +3471,6 @@ bool VSTPluginInstance::updateSizeFromEditor ([[maybe_unused]] int w, [[maybe_un //============================================================================== // entry point for all callbacks from the plugin -static pointer_sized_int VSTCALLBACK audioMaster (Vst2::AEffect* effect, int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) -{ - if (effect != nullptr) - if (auto* instance = (VSTPluginInstance*) (effect->resvd2)) - return instance->handleCallback (opcode, index, value, ptr, opt); - - return VSTPluginInstance::handleGeneralCallback (opcode, index, value, ptr, opt); -} - //============================================================================== VSTPluginFormat::VSTPluginFormat() {} VSTPluginFormat::~VSTPluginFormat() {} From d4107836cd8041d77641580fd00bdff465a88e07 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 18 Nov 2024 21:01:52 +0000 Subject: [PATCH 009/557] Grid: Convert nonstatic member function to static --- modules/juce_gui_basics/layout/juce_Grid.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/juce_gui_basics/layout/juce_Grid.cpp b/modules/juce_gui_basics/layout/juce_Grid.cpp index 2268cb11b6..650dcf9d9e 100644 --- a/modules/juce_gui_basics/layout/juce_Grid.cpp +++ b/modules/juce_gui_basics/layout/juce_Grid.cpp @@ -633,6 +633,8 @@ struct Grid::Helpers //============================================================================== struct AutoPlacement { + AutoPlacement() = delete; + using ItemPlacementArray = Array>; //============================================================================== @@ -841,7 +843,7 @@ struct Grid::Helpers } //============================================================================== - ItemPlacementArray deduceAllItems (Grid& grid) const + static ItemPlacementArray deduceAllItems (Grid& grid) { const auto namedAreas = PlacementHelpers::deduceNamedAreas (grid.templateAreas); @@ -1112,7 +1114,7 @@ float Grid::TrackInfo::getAbsoluteSize (float relativeFractionalUnit) const //============================================================================== void Grid::performLayout (Rectangle targetArea) { - const auto itemsAndAreas = Helpers::AutoPlacement().deduceAllItems (*this); + const auto itemsAndAreas = Helpers::AutoPlacement::deduceAllItems (*this); auto implicitTracks = Helpers::AutoPlacement::createImplicitTracks (*this, itemsAndAreas); From 8fa09ae8ab826c6e6d5d7657b3ce9dcd5203362a Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 18 Nov 2024 21:08:38 +0000 Subject: [PATCH 010/557] Grid: Refactor to move columnFirst data member to set Comparator --- modules/juce_gui_basics/layout/juce_Grid.cpp | 37 +++++++++----------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/modules/juce_gui_basics/layout/juce_Grid.cpp b/modules/juce_gui_basics/layout/juce_Grid.cpp index 650dcf9d9e..4664dcc102 100644 --- a/modules/juce_gui_basics/layout/juce_Grid.cpp +++ b/modules/juce_gui_basics/layout/juce_Grid.cpp @@ -651,7 +651,7 @@ struct Grid::Helpers { for (int i = 0; i < columnSpan; i++) for (int j = 0; j < rowSpan; j++) - setCell (cell.column + i, cell.row + j); + occupiedCells.insert ({ cell.column + i, cell.row + j }); return { { cell.column, cell.column + columnSpan }, { cell.row, cell.row + rowSpan } }; } @@ -700,36 +700,30 @@ struct Grid::Helpers } private: - struct SortableCell + struct Comparator { - int column, row; - bool columnFirst; - - bool operator< (const SortableCell& other) const + bool operator() (const Cell& a, const Cell& b) const { if (columnFirst) { - if (row == other.row) - return column < other.column; + if (a.row == b.row) + return a.column < b.column; - return row < other.row; + return a.row < b.row; } - if (row == other.row) - return column < other.column; + if (a.row == b.row) + return a.column < b.column; - return row < other.row; + return a.row < b.row; } - }; - void setCell (int column, int row) - { - occupiedCells.insert ({ column, row, columnFirst }); - } + const bool columnFirst; + }; bool isOccupied (Cell cell) const { - return occupiedCells.count ({ cell.column, cell.row, columnFirst }) > 0; + return occupiedCells.count (cell) > 0; } bool isOccupied (Cell cell, int columnSpan, int rowSpan) const @@ -780,8 +774,11 @@ struct Grid::Helpers } int highestCrossDimension; - bool columnFirst; - std::set occupiedCells; + const bool columnFirst; + std::set occupiedCells { Comparator { columnFirst } }; + + JUCE_DECLARE_NON_COPYABLE (OccupancyPlane) + JUCE_DECLARE_NON_MOVEABLE (OccupancyPlane) }; //============================================================================== From b72e43620ccb63a5b39fa6982a92eca2bc189dcc Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 18 Nov 2024 21:14:44 +0000 Subject: [PATCH 011/557] Grid: Fix cell ordering comparison Previously, the set Comparator behaved the same way, regardless of the value of columnFirst. This is incorrect; the set should be sorted such that the final item in the set has the greatest cross-dimension. There was also an off-by-one error in the result of getHighestCrossDimension(). The highestCrossDimension data member is exclusive, but the cell values in the occupiedCells set are inclusive. We need to add 1 to the maximum cell cross-dimension in order to convert it to an exclusive value. --- modules/juce_gui_basics/layout/juce_Grid.cpp | 115 +++++++++++++++---- 1 file changed, 95 insertions(+), 20 deletions(-) diff --git a/modules/juce_gui_basics/layout/juce_Grid.cpp b/modules/juce_gui_basics/layout/juce_Grid.cpp index 4664dcc102..5d78080d7e 100644 --- a/modules/juce_gui_basics/layout/juce_Grid.cpp +++ b/modules/juce_gui_basics/layout/juce_Grid.cpp @@ -702,23 +702,19 @@ struct Grid::Helpers private: struct Comparator { + using Tie = std::tuple (*) (const Cell&); + + explicit Comparator (bool columnFirstIn) + : tie (columnFirstIn ? Tie { [] (const Cell& x) { return std::tuple (x.row, x.column); } } + : Tie { [] (const Cell& x) { return std::tuple (x.column, x.row); } }) + {} + bool operator() (const Cell& a, const Cell& b) const { - if (columnFirst) - { - if (a.row == b.row) - return a.column < b.column; - - return a.row < b.row; - } - - if (a.row == b.row) - return a.column < b.column; - - return a.row < b.row; + return tie (a) < tie (b); } - const bool columnFirst; + const Tie tie; }; bool isOccupied (Cell cell) const @@ -746,20 +742,20 @@ struct Grid::Helpers int getHighestCrossDimension() const { - Cell cell { 1, 1 }; + const auto cell = occupiedCells.empty() ? Cell { 1, 1 } + : *std::prev (occupiedCells.end()); - if (occupiedCells.size() > 0) - cell = { occupiedCells.crbegin()->column, occupiedCells.crbegin()->row }; - - return std::max (getCrossDimension (cell), highestCrossDimension); + return std::max (getCrossDimension (cell) + 1, highestCrossDimension); } Cell advance (Cell cell) const { - if ((getCrossDimension (cell) + 1) >= getHighestCrossDimension()) + const auto next = getCrossDimension (cell) + 1; + + if (next >= getHighestCrossDimension()) return fromDimensions (getMainDimension (cell) + 1, 1); - return fromDimensions (getMainDimension (cell), getCrossDimension (cell) + 1); + return fromDimensions (getMainDimension (cell), next); } int getMainDimension (Cell cell) const { return columnFirst ? cell.column : cell.row; } @@ -1640,6 +1636,85 @@ struct GridTests final : public UnitTest evaluateInvariants (randomSolution); } } + + { + beginTest ("Cell orderings for row and columns work correctly"); + + const Rectangle bounds { 0, 0, 200, 200 }; + + Grid grid; + grid.autoColumns = Grid::TrackInfo { Grid::Fr { 1 } }; + grid.autoRows = Grid::TrackInfo { Grid::Fr { 1 } }; + + grid.items = { GridItem{}.withArea (1, 20), + GridItem{}.withArea (2, 10), + GridItem{}.withArea (GridItem::Span { 1 }, GridItem::Span { 15 }), + GridItem{} }; + + grid.autoFlow = Grid::AutoFlow::row; + grid.performLayout (bounds); + expect (grid.items.getLast().currentBounds == Rect { 150, 0, 10, 100 }); + + grid.autoFlow = Grid::AutoFlow::column; + grid.performLayout (bounds); + expect (grid.items.getLast().currentBounds == Rect { 0, 100, 10, 100 }); + + grid.items = { GridItem{}.withArea (20, 1), + GridItem{}.withArea (10, 2), + GridItem{}.withArea (GridItem::Span { 15 }, GridItem::Span { 1 }), + GridItem{} }; + + grid.autoFlow = Grid::AutoFlow::row; + grid.performLayout (bounds); + expect (grid.items.getLast().currentBounds == Rect { 100, 0, 100, 10 }); + + grid.autoFlow = Grid::AutoFlow::column; + grid.performLayout (bounds); + expect (grid.items.getLast().currentBounds == Rect { 0, 150, 100, 10 }); + } + + beginTest ("Complex grid layout"); + { + Grid grid; + + using Track = Grid::TrackInfo; + + grid.templateRows = { Track (1_fr), Track (1_fr), Track (1_fr) }; + grid.templateColumns = { Track (1_fr), Track (1_fr), Track (1_fr) }; + + grid.autoColumns = Track (1_fr); + grid.autoRows = Track (1_fr); + + grid.autoFlow = Grid::AutoFlow::column; + + grid.items.addArray ({ GridItem().withArea (2, 2, 4, 4), + GridItem(), + GridItem().withArea ({}, 3), + GridItem(), + GridItem().withArea (GridItem::Span (2), {}), + GridItem(), + GridItem(), + GridItem(), + GridItem(), + GridItem(), + GridItem(), + GridItem() }); + + grid.performLayout ({ 60, 30 }); + + expect (grid.items[0] .currentBounds == Rect { 10, 10, 20, 20 }); + expect (grid.items[1] .currentBounds == Rect { 0, 0, 10, 10 }); + expect (grid.items[2] .currentBounds == Rect { 20, 0, 10, 10 }); + expect (grid.items[3] .currentBounds == Rect { 0, 10, 10, 10 }); + expect (grid.items[4] .currentBounds == Rect { 30, 0, 10, 20 }); + expect (grid.items[5] .currentBounds == Rect { 30, 20, 10, 10 }); + expect (grid.items[6] .currentBounds == Rect { 40, 0, 10, 10 }); + expect (grid.items[7] .currentBounds == Rect { 40, 10, 10, 10 }); + expect (grid.items[8] .currentBounds == Rect { 40, 20, 10, 10 }); + expect (grid.items[9] .currentBounds == Rect { 50, 0, 10, 10 }); + expect (grid.items[10].currentBounds == Rect { 50, 10, 10, 10 }); + expect (grid.items[11].currentBounds == Rect { 50, 20, 10, 10 }); + } } }; From 9de56d0aabbe8a471a790c691c3942dd3b7548df Mon Sep 17 00:00:00 2001 From: Christian Haase Date: Fri, 15 Nov 2024 10:09:06 +0100 Subject: [PATCH 012/557] PluginListComponent: Add missing `TRANS` statements --- .../scanning/juce_PluginListComponent.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp index 3c748685d9..e1484c1518 100644 --- a/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp +++ b/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -404,7 +404,7 @@ PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, Kno : formatManager (manager), list (listToEdit), deadMansPedalFile (deadMansPedal), - optionsButton ("Options..."), + optionsButton (TRANS ("Options...")), propertiesToUse (props), allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), numThreads (allowAsync ? 1 : 0) @@ -554,7 +554,7 @@ PopupMenu PluginListComponent::createOptionsMenu() for (auto format : formatManager.getFormats()) if (format->canScanForPlugins()) - menu.addItem (PopupMenu::Item ("Remove all " + format->getName() + " plug-ins") + menu.addItem (PopupMenu::Item (TRANS ("Remove all XFMTX plug-ins").replace ("XFMTX", format->getName())) .setEnabled (! list.getTypesForFormat (*format).isEmpty()) .setAction ([this, format] { @@ -583,7 +583,7 @@ PopupMenu PluginListComponent::createOptionsMenu() for (auto format : formatManager.getFormats()) if (format->canScanForPlugins()) - menu.addItem (PopupMenu::Item ("Scan for new or updated " + format->getName() + " plug-ins") + menu.addItem (PopupMenu::Item (TRANS ("Scan for new or updated XFMTX plug-ins").replace ("XFMTX", format->getName())) .setAction ([this, format] { scanFor (*format); })); return menu; From 55fb6dbe621a1855f2b4900c357c9fb99f98afed Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 18 Nov 2024 21:54:08 +0000 Subject: [PATCH 013/557] iOS Audio: Ensure current sampleRate and bufferSize are always updated after querying sample rates The code added in 6f20de54349470aff16453052a82aa7e8e0aea26 was only executed when no explicit sample rates were set. Now, the sample rate is always updated after querying available sample rates and before querying available buffer sizes, so that the buffer size check is guaranteed to use an up-to-date samplerate value. --- modules/juce_audio_devices/native/juce_Audio_ios.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/juce_audio_devices/native/juce_Audio_ios.cpp b/modules/juce_audio_devices/native/juce_Audio_ios.cpp index 709777cec0..e7b1c5fbe1 100644 --- a/modules/juce_audio_devices/native/juce_Audio_ios.cpp +++ b/modules/juce_audio_devices/native/juce_Audio_ios.cpp @@ -518,10 +518,6 @@ struct iOSAudioIODevice::Pimpl final : public AsyncUpdater availableSampleRates.addIfNotAlreadyThere (highestRate); - // Reset sample rate back to the original, so that we don't end up stuck on the highest rate - sampleRate = trySampleRate (sampleRate); - bufferSize = getBufferSize (sampleRate); - AudioUnitAddPropertyListener (audioUnit, kAudioUnitProperty_StreamFormat, dispatchAudioUnitPropertyChange, @@ -553,6 +549,13 @@ struct iOSAudioIODevice::Pimpl final : public AsyncUpdater JUCE_IOS_AUDIO_LOG ("Updating hardware info"); updateAvailableSampleRates(); + + // The sample rate and buffer size may have been affected by + // updateAvailableSampleRates(), so try restoring the last good + // sample rate + sampleRate = trySampleRate (sampleRate); + bufferSize = getBufferSize (sampleRate); + updateAvailableBufferSizes(); if (deviceType != nullptr) From 49477bf0e50990eb7e78d49f656c8d73c4eea02b Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 20 Nov 2024 13:04:06 +0000 Subject: [PATCH 014/557] LICENSE: Update path to choc --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index fa98770365..39e3c57e17 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -42,7 +42,7 @@ The JUCE modules contain the following dependencies: - [GLEW](modules/juce_opengl/opengl/juce_gl.h) ([BSD](modules/juce_opengl/opengl/juce_gl.h)), including [Mesa](modules/juce_opengl/opengl/juce_gl.h) ([MIT](modules/juce_opengl/opengl/juce_gl.h)) and [Khronos](modules/juce_opengl/opengl/juce_gl.h) ([MIT](modules/juce_opengl/opengl/juce_gl.h)) - [Ogg Vorbis](modules/juce_audio_formats/codecs/oggvorbis/) ([BSD](modules/juce_audio_formats/codecs/oggvorbis/Ogg%20Vorbis%20Licence.txt)) - [jpeglib](modules/juce_graphics/image_formats/jpglib/) ([Independent JPEG Group License](modules/juce_graphics/image_formats/jpglib/README)) -- [CHOC](modules/juce_core/javascript/choc/) ([ISC](modules/juce_core/javascript/choc/LICENSE.md)), including [QuickJS](modules/juce_core/javascript/choc/javascript/choc_javascript_QuickJS.h) ([MIT](modules/juce_core/javascript/choc/javascript/choc_javascript_QuickJS.h)) +- [CHOC](modules/juce_javascript/choc/) ([ISC](modules/juce_javascript/choc/LICENSE.md)), including [QuickJS](modules/juce_javascript/choc/javascript/choc_javascript_QuickJS.h) ([MIT](modules/juce_javascript/choc/javascript/choc_javascript_QuickJS.h)) - [LV2](modules/juce_audio_processors/format_types/LV2_SDK/) ([ISC](modules/juce_audio_processors/format_types/LV2_SDK/lv2/COPYING)) - [pslextensions](modules/juce_audio_processors/format_types/pslextensions/ipslcontextinfo.h) ([Public domain](modules/juce_audio_processors/format_types/pslextensions/ipslcontextinfo.h)) - [AAX](modules/juce_audio_plugin_client/AAX/SDK/) ([Proprietary Avid AAX License/GPLv3](modules/juce_audio_plugin_client/AAX/SDK/LICENSE.txt)) From f0928ebd6e3ff1303785abbc1aadf134b628336f Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 21 Nov 2024 22:19:29 +0000 Subject: [PATCH 015/557] Windowing: Fix link of UserNotifications framework on iOS Since eb6ebaf5, juce_gui_basics always depends on UserNotifications when building for iOS. --- modules/juce_gui_basics/juce_gui_basics.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/juce_gui_basics/juce_gui_basics.h b/modules/juce_gui_basics/juce_gui_basics.h index d15687204f..387927ed0a 100644 --- a/modules/juce_gui_basics/juce_gui_basics.h +++ b/modules/juce_gui_basics/juce_gui_basics.h @@ -55,7 +55,7 @@ OSXFrameworks: Cocoa QuartzCore WeakOSXFrameworks: Metal MetalKit iOSFrameworks: CoreServices UIKit - WeakiOSFrameworks: Metal MetalKit UniformTypeIdentifiers + WeakiOSFrameworks: Metal MetalKit UniformTypeIdentifiers UserNotifications END_JUCE_MODULE_DECLARATION From 39b335ccef6c545efa71649e009d9cce6633496d Mon Sep 17 00:00:00 2001 From: tpoole Date: Fri, 22 Nov 2024 11:14:10 +0000 Subject: [PATCH 016/557] Make building with MinGW a compiler error --- extras/Build/CMake/JUCEHelperTargets.cmake | 6 +++--- extras/Build/CMake/JUCEModuleSupport.cmake | 10 +--------- extras/Build/CMake/JUCEUtils.cmake | 9 --------- modules/juce_core/native/juce_Threads_windows.cpp | 2 +- modules/juce_core/system/juce_TargetPlatform.h | 8 +------- 5 files changed, 6 insertions(+), 29 deletions(-) diff --git a/extras/Build/CMake/JUCEHelperTargets.cmake b/extras/Build/CMake/JUCEHelperTargets.cmake index 244cccec71..051296daea 100644 --- a/extras/Build/CMake/JUCEHelperTargets.cmake +++ b/extras/Build/CMake/JUCEHelperTargets.cmake @@ -140,9 +140,9 @@ if((CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") OR (CMAKE_CXX_COMPILER_FRONTEND_VARIA $<$:$,-GL,-flto>>) target_link_libraries(juce_recommended_lto_flags INTERFACE $<$:$<$:-LTCG>>) -elseif((NOT MINGW) AND ((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") - OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))) +elseif((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")) target_compile_options(juce_recommended_lto_flags INTERFACE $<$:-flto>) target_link_libraries(juce_recommended_lto_flags INTERFACE $<$:-flto>) # Xcode 15.0 requires this flag to avoid a compiler bug diff --git a/extras/Build/CMake/JUCEModuleSupport.cmake b/extras/Build/CMake/JUCEModuleSupport.cmake index 0d93d36033..03378a2090 100644 --- a/extras/Build/CMake/JUCEModuleSupport.cmake +++ b/extras/Build/CMake/JUCEModuleSupport.cmake @@ -68,9 +68,7 @@ endfunction() if((CMAKE_SYSTEM_NAME STREQUAL "Windows") OR (CMAKE_SYSTEM_NAME STREQUAL "Linux") - OR (CMAKE_SYSTEM_NAME MATCHES ".*BSD") - OR MSYS - OR MINGW) + OR (CMAKE_SYSTEM_NAME MATCHES ".*BSD")) # If you really need to override the detected arch for some reason, # you can configure the build with -DJUCE_TARGET_ARCHITECTURE= if(NOT DEFINED JUCE_TARGET_ARCHITECTURE) @@ -654,12 +652,6 @@ function(juce_add_module module_path) endif() _juce_link_libs_from_metadata("${module_name}" "${metadata_dict}" windowsLibs) - elseif(MSYS OR MINGW) - if(module_name STREQUAL "juce_gui_basics") - target_compile_options(${module_name} INTERFACE "-Wa,-mbig-obj") - endif() - - _juce_link_libs_from_metadata("${module_name}" "${metadata_dict}" mingwLibs) endif() endif() diff --git a/extras/Build/CMake/JUCEUtils.cmake b/extras/Build/CMake/JUCEUtils.cmake index 8a6b0d6e15..8daccbaa5f 100644 --- a/extras/Build/CMake/JUCEUtils.cmake +++ b/extras/Build/CMake/JUCEUtils.cmake @@ -1051,10 +1051,6 @@ function(_juce_add_vst3_manifest_helper_target) target_compile_options(juce_vst3_helper PRIVATE -fobjc-arc) endif() - if(MSYS OR MINGW) - target_link_options(juce_vst3_helper PRIVATE -municode) - endif() - set_target_properties(juce_vst3_helper PROPERTIES BUILD_WITH_INSTALL_RPATH ON) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) @@ -1083,11 +1079,6 @@ function(juce_enable_vst3_manifest_step shared_code_target) "juce_enable_copy_plugin_step too.") endif() - if((MSYS OR MINGW) AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9) - message(WARNING "VST3 manifest generation is disabled for ${shared_code_target} because the compiler is not supported.") - return() - endif() - if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND NOT JUCE_WINDOWS_HELPERS_CAN_RUN) message(WARNING "VST3 manifest generation is disabled for ${shared_code_target} because a " "${JUCE_TARGET_ARCHITECTURE} manifest helper cannot run on a host system processor detected to be " diff --git a/modules/juce_core/native/juce_Threads_windows.cpp b/modules/juce_core/native/juce_Threads_windows.cpp index be21a44c63..a459a7800e 100644 --- a/modules/juce_core/native/juce_Threads_windows.cpp +++ b/modules/juce_core/native/juce_Threads_windows.cpp @@ -325,7 +325,7 @@ void DynamicLibrary::close() void* DynamicLibrary::getFunction (const String& functionName) noexcept { - return handle != nullptr ? (void*) GetProcAddress ((HMODULE) handle, functionName.toUTF8()) // (void* cast is required for mingw) + return handle != nullptr ? (void*) GetProcAddress ((HMODULE) handle, functionName.toUTF8()) : nullptr; } diff --git a/modules/juce_core/system/juce_TargetPlatform.h b/modules/juce_core/system/juce_TargetPlatform.h index 0c55ad7c58..ee433d3387 100644 --- a/modules/juce_core/system/juce_TargetPlatform.h +++ b/modules/juce_core/system/juce_TargetPlatform.h @@ -110,13 +110,7 @@ #endif #ifdef __MINGW32__ - #define JUCE_MINGW 1 - #warning Support for MinGW has been removed. Please use an alternative compiler. - #ifdef __MINGW64__ - #define JUCE_64BIT 1 - #else - #define JUCE_32BIT 1 - #endif + #error "MinGW is not supported. Please use an alternative compiler." #endif /** If defined, this indicates that the processor is little-endian. */ From ccdd857662a2f0eccdbc5f473896d0409faa9766 Mon Sep 17 00:00:00 2001 From: tpoole Date: Fri, 22 Nov 2024 16:14:11 +0000 Subject: [PATCH 017/557] GitHub: Add more logging to the CLA check --- .github/workflows/check-cla.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check-cla.yml b/.github/workflows/check-cla.yml index 70d082413d..c657a4cff2 100644 --- a/.github/workflows/check-cla.yml +++ b/.github/workflows/check-cla.yml @@ -18,6 +18,7 @@ jobs: with urllib.request.urlopen(req) as response: return json.loads(response.read().decode('utf-8')) prCommits = jsonRequest('https://api.github.com/repos/juce-framework/JUCE/pulls/${{ github.event.number }}/commits') + print(f'Commit info:\n{json.dumps(prCommits, indent=4)}') allAuthors = [commit[authorType]['login'] for authorType in ['author', 'committer'] for commit in prCommits if commit[authorType]] uniqueAuthors = [name for name in list(set(allAuthors)) if name != 'web-flow'] if (len(uniqueAuthors) == 0): From af51cb46eb394ceca381bcc9190b42274b292fb1 Mon Sep 17 00:00:00 2001 From: Oliver James Date: Tue, 1 Oct 2024 10:55:12 +0100 Subject: [PATCH 018/557] Projucer: Remove ARM32 support on Windows --- BREAKING_CHANGES.md | 17 +++++++++++++ .../Projucer/Source/Project/jucer_Project.h | 5 +++- .../ProjectSaving/jucer_ProjectExport_MSVC.h | 24 +++++++++++++++---- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 2c1cee87ee..0fddbf7694 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -2,6 +2,23 @@ # Version 8.0.4 +## Change + +Support for Arm32 in Projucer has been removed for Windows targets. + +**Possible Issues** + +Projucer projects targeting Arm32 on Windows will no longer be able to select that option. + +**Workaround** + +Select Arm64 or Arm64EC instead of Arm32, and port any 32-bit specific code to 64-bit. + +**Rationale** + +32-bit Arm support has been deprecated in current versions of Windows 11. + + ## Change The Javascript implementation has been moved into a independent juce module. diff --git a/extras/Projucer/Source/Project/jucer_Project.h b/extras/Projucer/Source/Project/jucer_Project.h index 84fc56c563..a3ce2124bc 100644 --- a/extras/Projucer/Source/Project/jucer_Project.h +++ b/extras/Projucer/Source/Project/jucer_Project.h @@ -59,6 +59,7 @@ namespace ProjectMessages DECLARE_ID (pluginCodeInvalid); DECLARE_ID (manufacturerCodeInvalid); DECLARE_ID (deprecatedExporter); + DECLARE_ID (unsupportedArm32Config); DECLARE_ID (notification); DECLARE_ID (warning); @@ -73,7 +74,7 @@ namespace ProjectMessages static Identifier warnings[] = { Ids::cppStandard, Ids::moduleNotFound, Ids::jucePath, Ids::jucerFileModified, Ids::missingModuleDependencies, Ids::oldProjucer, Ids::pluginCodeInvalid, Ids::manufacturerCodeInvalid, - Ids::deprecatedExporter }; + Ids::deprecatedExporter, Ids::unsupportedArm32Config }; if (std::find (std::begin (warnings), std::end (warnings), message) != std::end (warnings)) return Ids::warning; @@ -99,6 +100,7 @@ namespace ProjectMessages if (message == Ids::pluginCodeInvalid) return "Invalid Plugin Code"; if (message == Ids::manufacturerCodeInvalid) return "Invalid Manufacturer Code"; if (message == Ids::deprecatedExporter) return "Deprecated Exporter"; + if (message == Ids::unsupportedArm32Config) return "Unsupported Architecture"; jassertfalse; return {}; @@ -116,6 +118,7 @@ namespace ProjectMessages if (message == Ids::pluginCodeInvalid) return "The plugin code should be exactly four characters in length."; if (message == Ids::manufacturerCodeInvalid) return "The manufacturer code should be exactly four characters in length."; if (message == Ids::deprecatedExporter) return "The project includes a deprecated exporter."; + if (message == Ids::unsupportedArm32Config) return "The project includes a Visual Studio configuration that uses the 32-bit Arm architecture, which is no longer supported. This configuration has been hidden, and will be removed on save."; jassertfalse; return {}; diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index 626df52908..455d8d113e 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -148,6 +148,24 @@ public: config->getValue (Ids::targetName) = oldStyleLibName; } + { + std::vector toErase; + + for (const auto& config : getConfigurations()) + { + if (config.getProperty (Ids::winArchitecture) == "ARM") + toErase.push_back (config); + } + + if (! toErase.empty()) + { + for (const auto& e : toErase) + e.getParent().removeChild (e, nullptr); + + getProject().addProjectMessage (ProjectMessages::Ids::unsupportedArm32Config, {}); + } + } + for (ConfigIterator i (*this); i.next();) dynamic_cast (*i).updateOldLTOSetting(); } @@ -214,7 +232,6 @@ public: String getIntel64BitArchName() const { return "x64"; } String getIntel32BitArchName() const { return "Win32"; } String getArm64BitArchName() const { return "ARM64"; } - String getArm32BitArchName() const { return "ARM"; } String getArchitectureString() const { return architectureTypeValue.get(); } String getDebugInformationFormatString() const { return debugInformationFormatValue.get(); } @@ -258,8 +275,8 @@ public: addVisualStudioPluginInstallPathProperties (props); props.add (new ChoicePropertyComponent (architectureTypeValue, "Architecture", - { getIntel32BitArchName(), getIntel64BitArchName(), getArm32BitArchName(), getArm64BitArchName() }, - { getIntel32BitArchName(), getIntel64BitArchName(), getArm32BitArchName(), getArm64BitArchName() }), + { getIntel32BitArchName(), getIntel64BitArchName(), getArm64BitArchName() }, + { getIntel32BitArchName(), getIntel64BitArchName(), getArm64BitArchName() }), "Which Windows architecture to use."); props.add (new ChoicePropertyComponentWithEnablement (debugInformationFormatValue, @@ -399,7 +416,6 @@ public: { { "Win32", { "%programfiles(x86)%", "%CommonProgramFiles(x86)%" } }, { "x64", { "%ProgramW6432%", "%CommonProgramW6432%" } }, - { "ARM", { "%programfiles(arm)%", "%CommonProgramFiles(arm)%" } }, { "ARM64", { "%ProgramW6432%", "%CommonProgramW6432%" } } }; From ac0ebe5797a3dcc51d6c04374aa562690fc802f1 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 28 Oct 2024 13:26:44 +0000 Subject: [PATCH 019/557] Projucer: Add Arm64(EC) support on Windows --- .../ProjectSaving/jucer_ProjectExport_MSVC.h | 1000 ++++++++++------- 1 file changed, 614 insertions(+), 386 deletions(-) diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index 455d8d113e..18fc4b4974 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -53,6 +53,45 @@ inline StringArray msBuildEscape (StringArray range) return range; } +enum class Architecture +{ + win32, + win64, + arm64, + arm64ec +}; + +static String toString (Architecture arch) +{ + switch (arch) + { + case Architecture::win32: return "Win32"; + case Architecture::win64: return "x64"; + case Architecture::arm64: return "ARM64"; + case Architecture::arm64ec: return "ARM64EC"; + } + + jassertfalse; + return ""; +} + +static std::optional architectureTypeFromString (const String& string) +{ + constexpr Architecture values[] { Architecture::win32, + Architecture::win64, + Architecture::arm64, + Architecture::arm64ec }; + + for (auto value : values) + { + if (toString (value) == string) + return value; + } + + jassertfalse; + return {}; +} + //============================================================================== class MSVCProjectExporterBase : public ProjectExporter { @@ -125,7 +164,7 @@ public: if (oldStylePrebuildCommand.isNotEmpty()) for (ConfigIterator config (*this); config.next();) - dynamic_cast (*config).getValue (Ids::prebuildCommand) = oldStylePrebuildCommand; + static_cast (&*config)->getValue (Ids::prebuildCommand) = oldStylePrebuildCommand; } { @@ -167,7 +206,28 @@ public: } for (ConfigIterator i (*this); i.next();) - dynamic_cast (*i).updateOldLTOSetting(); + { + auto& config = *static_cast (&*i); + config.updateOldLTOSetting(); + config.updateOldArchSetting(); + } + } + + Array getAllActiveArchitectures() const + { + Array archs; + + for (ConstConfigIterator i (*this); i.next();) + { + const auto& config = *static_cast (&*i); + const auto configArchs = config.getArchitectures(); + + for (const auto& arch : configArchs) + if (! archs.contains (arch)) + archs.add (arch); + } + + return archs; } void initialiseDependencyPathValues() override @@ -183,8 +243,7 @@ public: } //============================================================================== - class MSVCBuildConfiguration final : public BuildConfiguration, - private Value::Listener + class MSVCBuildConfiguration final : public BuildConfiguration { public: MSVCBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e) @@ -199,7 +258,7 @@ public: multiProcessorCompilationValue (config, Ids::multiProcessorCompilation, getUndoManager(), true), intermediatesPathValue (config, Ids::intermediatesPath, getUndoManager()), characterSetValue (config, Ids::characterSet, getUndoManager()), - architectureTypeValue (config, Ids::winArchitecture, getUndoManager(), getIntel64BitArchName()), + architectureTypeValue (config, Ids::winArchitecture, getUndoManager(), Array { toString (Architecture::win64) }, ","), fastMathValue (config, Ids::fastMath, getUndoManager()), debugInformationFormatValue (config, Ids::debugInformationFormat, getUndoManager(), isDebug() ? "ProgramDatabase" : "None"), pluginBinaryCopyStepValue (config, Ids::enablePluginBinaryCopyStep, getUndoManager(), false), @@ -207,32 +266,68 @@ public: vst3BinaryLocation (config, Ids::vst3BinaryLocation, getUndoManager()), aaxBinaryLocation (config, Ids::aaxBinaryLocation, getUndoManager()), lv2BinaryLocation (config, Ids::lv2BinaryLocation, getUndoManager()), - unityPluginBinaryLocation (config, Ids::unityPluginBinaryLocation, getUndoManager(), {}) + unityPluginBinaryLocation (config, Ids::unityPluginBinaryLocation, getUndoManager()) { - setPluginBinaryCopyLocationDefaults(); - optimisationLevelValue.setDefault (isDebug() ? optimisationOff : optimiseFull); + constexpr std::tuple paths[] + { + { Architecture::win32, "%programfiles(x86)%", "%CommonProgramFiles(x86)%" }, + { Architecture::win64, "%ProgramW6432%", "%CommonProgramW6432%" }, + { Architecture::arm64, "%ProgramW6432%", "%CommonProgramW6432%" }, + { Architecture::arm64ec, "%ProgramW6432%", "%CommonProgramW6432%" } + }; - architectureValueToListenTo = architectureTypeValue.getPropertyAsValue(); - architectureValueToListenTo.addListener (this); + for (const auto& [arch, programFolderPath, commonFolderPath] : paths) + { + setBinaryPathDefault (Ids::vstBinaryLocation, arch, programFolderPath + String ("\\Steinberg\\Vstplugins")); + setBinaryPathDefault (Ids::vst3BinaryLocation, arch, commonFolderPath + String ("\\VST3")); + setBinaryPathDefault (Ids::aaxBinaryLocation, arch, commonFolderPath + String ("\\Avid\\Audio\\Plug-Ins")); + setBinaryPathDefault (Ids::lv2BinaryLocation, arch, "%APPDATA%\\LV2"); + } + + optimisationLevelValue.setDefault (isDebug() ? optimisationOff : optimiseFull); + } + + String getBinaryPath (const Identifier& id, Architecture arch) const + { + if (auto* location = getLocationForArchitecture (id, arch)) + return location->get().toString(); + + return ""; + } + + void setBinaryPathDefault (const Identifier& id, Architecture arch, const String& path) + { + if (auto* location = getLocationForArchitecture (id, arch)) + location->setDefault (path); } //============================================================================== int getWarningLevel() const { return warningLevelValue.get(); } bool areWarningsTreatedAsErrors() const { return warningsAreErrorsValue.get(); } + Array getArchitectures() const + { + auto value = architectureTypeValue.get(); + auto* array = value.getArray(); + + if (array == nullptr) + return {}; + + Array result; + result.resize (array->size()); + + std::transform (array->begin(), array->end(), result.begin(), [] (const var& archVar) + { + return *architectureTypeFromString (archVar.toString()); + }); + + return result; + } + String getPrebuildCommandString() const { return prebuildCommandValue.get(); } String getPostbuildCommandString() const { return postbuildCommandValue.get(); } - String getVSTBinaryLocationString() const { return vstBinaryLocation.get(); } - String getVST3BinaryLocationString() const { return vst3BinaryLocation.get(); } - String getAAXBinaryLocationString() const { return aaxBinaryLocation.get();} - String getLV2BinaryLocationString() const { return lv2BinaryLocation.get();} - String getUnityPluginBinaryLocationString() const { return unityPluginBinaryLocation.get(); } String getIntermediatesPathString() const { return intermediatesPathValue.get(); } String getCharacterSetString() const { return characterSetValue.get(); } - String getIntel64BitArchName() const { return "x64"; } - String getIntel32BitArchName() const { return "Win32"; } - String getArm64BitArchName() const { return "ARM64"; } - String getArchitectureString() const { return architectureTypeValue.get(); } String getDebugInformationFormatString() const { return debugInformationFormatValue.get(); } bool shouldGenerateDebugSymbols() const { return generateDebugSymbolsValue.get(); } @@ -243,9 +338,9 @@ public: bool isPluginBinaryCopyStepEnabled() const { return pluginBinaryCopyStepValue.get(); } //============================================================================== - String createMSVCConfigName() const + String createMSVCConfigName (Architecture arch) const { - return getName() + "|" + getArchitectureString(); + return getName() + "|" + toString (arch); } String getOutputFilename (const String& suffix, @@ -274,9 +369,20 @@ public: if (project.isAudioPluginProject()) addVisualStudioPluginInstallPathProperties (props); - props.add (new ChoicePropertyComponent (architectureTypeValue, "Architecture", - { getIntel32BitArchName(), getIntel64BitArchName(), getArm64BitArchName() }, - { getIntel32BitArchName(), getIntel64BitArchName(), getArm64BitArchName() }), + const auto architectureList = exporter.getExporterIdentifier() == Identifier { "VS2022" } + ? std::vector { Architecture::win32, Architecture::win64, Architecture::arm64, Architecture::arm64ec } + : std::vector { Architecture::win32, Architecture::win64, Architecture::arm64 }; + + Array architectureListAsStrings; + Array architectureListAsVars; + + for (const auto& arch : architectureList) + { + architectureListAsStrings.add (toString (arch)); + architectureListAsVars.add (toString (arch)); + } + + props.add (new MultiChoicePropertyComponent (architectureTypeValue, "Architecture", architectureListAsStrings, architectureListAsVars), "Which Windows architecture to use."); props.add (new ChoicePropertyComponentWithEnablement (debugInformationFormatValue, @@ -361,15 +467,111 @@ public: linkTimeOptimisationValue = (static_cast (config ["wholeProgramOptimisation"]) == 0); } + void updateOldArchSetting() + { + if (architectureTypeValue.get().isArray()) + return; + + const auto archString = architectureTypeValue.get().toString(); + const auto archType = architectureTypeFromString (archString); + + if (! archType) + return; + + const auto pluginBinaryPathLocationIds = + { + Ids::vstBinaryLocation, + Ids::vst3BinaryLocation, + Ids::lv2BinaryLocation, + Ids::aaxBinaryLocation, + Ids::unityPluginBinaryLocation + }; + + for (const auto& location : pluginBinaryPathLocationIds) + { + if (auto* prop = config.getPropertyPointer (location)) + { + setBinaryPathDefault (location, *archType, prop->toString()); + } + } + + architectureTypeValue = Array { archString }; + } + private: ValueTreePropertyWithDefault warningLevelValue, warningsAreErrorsValue, prebuildCommandValue, postbuildCommandValue, generateDebugSymbolsValue, enableIncrementalLinkingValue, useRuntimeLibDLLValue, multiProcessorCompilationValue, intermediatesPathValue, characterSetValue, architectureTypeValue, fastMathValue, debugInformationFormatValue, pluginBinaryCopyStepValue; - ValueTreePropertyWithDefault vstBinaryLocation, vst3BinaryLocation, aaxBinaryLocation, lv2BinaryLocation, unityPluginBinaryLocation; + struct LocationProperties + { + LocationProperties (ValueTree& tree, const Identifier& propertyID, UndoManager* um) + : win32 (tree, propertyID + "_Win32", um), + x64 (tree, propertyID + "_x64", um), + arm64 (tree, propertyID + "_arm64", um), + arm64ec (tree, propertyID + "_arm64ec", um) + { + } - Value architectureValueToListenTo; + const ValueTreePropertyWithDefault* get (Architecture arch) const + { + return get (*this, arch); + } + + ValueTreePropertyWithDefault* get (Architecture arch) + { + return get (*this, arch); + } + + ValueTreePropertyWithDefault win32, x64, arm64, arm64ec; + + private: + template + static auto get (This& t, Architecture arch) -> decltype (t.get (arch)) + { + switch (arch) + { + case Architecture::win32: return &t.win32; + case Architecture::win64: return &t.x64; + case Architecture::arm64: return &t.arm64; + case Architecture::arm64ec: return &t.arm64ec; + } + + jassertfalse; + return nullptr; + } + }; + + template + static auto getLocationForArchitecture (This& t, const Identifier& id, Architecture arch) + { + const auto properties = + { + std::pair { Ids::vstBinaryLocation, &t.vstBinaryLocation }, + std::pair { Ids::vst3BinaryLocation, &t.vst3BinaryLocation }, + std::pair { Ids::aaxBinaryLocation, &t.aaxBinaryLocation }, + std::pair { Ids::lv2BinaryLocation, &t.lv2BinaryLocation } + }; + + const auto iter = std::find_if (properties.begin(), + properties.end(), + [id] (auto pair) { return id == pair.first; }); + + return iter != properties.end() ? iter->second->get (arch) : nullptr; + } + + ValueTreePropertyWithDefault* getLocationForArchitecture (const Identifier& id, Architecture arch) + { + return getLocationForArchitecture (*this, id, arch); + } + + const ValueTreePropertyWithDefault* getLocationForArchitecture (const Identifier& id, Architecture arch) const + { + return getLocationForArchitecture (*this, id, arch); + } + + LocationProperties vstBinaryLocation, vst3BinaryLocation, aaxBinaryLocation, lv2BinaryLocation, unityPluginBinaryLocation; //============================================================================== void addVisualStudioPluginInstallPathProperties (PropertyListBuilder& props) @@ -381,60 +583,33 @@ public: props.add (new ChoicePropertyComponent (pluginBinaryCopyStepValue, "Enable Plugin Copy Step"), "Enable this to copy plugin binaries to a specified folder after building."); + const auto addLocationProperties = [&] (auto& locationProps, const String& format) + { + for (const auto& [member, arch] : { std::tuple (&LocationProperties::win32, Architecture::win32), + std::tuple (&LocationProperties::x64, Architecture::win64), + std::tuple (&LocationProperties::arm64, Architecture::arm64), + std::tuple (&LocationProperties::arm64ec, Architecture::arm64ec) }) + { + const auto archAndFormat = toString (arch) + " " + format; + props.add (new TextPropertyComponentWithEnablement (locationProps.*member, pluginBinaryCopyStepValue, archAndFormat + " Binary Location", 1024, false), + "The folder in which the compiled " + archAndFormat + " binary should be placed."); + } + }; + if (project.shouldBuildVST3()) - props.add (new TextPropertyComponentWithEnablement (vst3BinaryLocation, pluginBinaryCopyStepValue, "VST3 Binary Location", - 1024, false), - "The folder in which the compiled VST3 binary should be placed."); + addLocationProperties (vst3BinaryLocation, "VST3"); if (project.shouldBuildAAX()) - props.add (new TextPropertyComponentWithEnablement (aaxBinaryLocation, pluginBinaryCopyStepValue, "AAX Binary Location", - 1024, false), - "The folder in which the compiled AAX binary should be placed."); + addLocationProperties (aaxBinaryLocation, "AAX"); if (project.shouldBuildLV2()) - props.add (new TextPropertyComponentWithEnablement (lv2BinaryLocation, pluginBinaryCopyStepValue, "LV2 Binary Location", - 1024, false), - "The folder in which the compiled LV2 binary should be placed."); + addLocationProperties (lv2BinaryLocation, "LV2"); if (project.shouldBuildUnityPlugin()) - props.add (new TextPropertyComponentWithEnablement (unityPluginBinaryLocation, pluginBinaryCopyStepValue, "Unity Binary Location", - 1024, false), - "The folder in which the compiled Unity plugin binary and associated C# GUI script should be placed."); + addLocationProperties (unityPluginBinaryLocation, "Unity"); if (project.shouldBuildVST()) - props.add (new TextPropertyComponentWithEnablement (vstBinaryLocation, pluginBinaryCopyStepValue, "VST (Legacy) Binary Location", - 1024, false), - "The folder in which the compiled legacy VST binary should be placed."); - - } - - void setPluginBinaryCopyLocationDefaults() - { - const auto [programsFolderPath, commonsFolderPath] = [&]() -> std::tuple - { - static const std::map> options - { - { "Win32", { "%programfiles(x86)%", "%CommonProgramFiles(x86)%" } }, - { "x64", { "%ProgramW6432%", "%CommonProgramW6432%" } }, - { "ARM64", { "%ProgramW6432%", "%CommonProgramW6432%" } } - }; - - if (const auto iter = options.find (getArchitectureString()); iter != options.cend()) - return iter->second; - - jassertfalse; - return { "%programfiles%", "%CommonProgramFiles%" }; - }(); - - vstBinaryLocation.setDefault (programsFolderPath + String ("\\Steinberg\\Vstplugins")); - vst3BinaryLocation.setDefault (commonsFolderPath + String ("\\VST3")); - aaxBinaryLocation.setDefault (commonsFolderPath + String ("\\Avid\\Audio\\Plug-Ins")); - lv2BinaryLocation.setDefault ("%APPDATA%\\LV2"); - } - - void valueChanged (Value&) override - { - setPluginBinaryCopyLocationDefaults(); + addLocationProperties (vstBinaryLocation, "VST (Legacy)"); } }; @@ -448,7 +623,7 @@ public: projectGuid = createGUID (owner.getProject().getProjectUIDString() + getName()); } - virtual ~MSVCTarget() {} + virtual ~MSVCTarget() = default; String getProjectVersionString() const { return "10.00"; } String getProjectFileSuffix() const { return ".vcxproj"; } @@ -462,17 +637,22 @@ public: projectXml.setAttribute ("ToolsVersion", getOwner().getToolsVersion()); projectXml.setAttribute ("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003"); + const auto allArchitectures = owner.getAllActiveArchitectures(); + { auto* configsGroup = projectXml.createNewChildElement ("ItemGroup"); configsGroup->setAttribute ("Label", "ProjectConfigurations"); for (ConstConfigIterator i (owner); i.next();) { - auto& config = dynamic_cast (*i); - auto* e = configsGroup->createNewChildElement ("ProjectConfiguration"); - e->setAttribute ("Include", config.createMSVCConfigName()); - e->createNewChildElement ("Configuration")->addTextElement (config.getName()); - e->createNewChildElement ("Platform")->addTextElement (config.getArchitectureString()); + auto& config = *static_cast (&*i); + for (const auto& arch : allArchitectures) + { + auto* e = configsGroup->createNewChildElement ("ProjectConfiguration"); + e->setAttribute ("Include", config.createMSVCConfigName (arch)); + e->createNewChildElement ("Configuration")->addTextElement (config.getName()); + e->createNewChildElement ("Platform")->addTextElement (toString (arch)); + } } } @@ -489,40 +669,43 @@ public: for (ConstConfigIterator i (owner); i.next();) { - auto& config = dynamic_cast (*i); + auto& config = *static_cast (&*i); - auto* e = projectXml.createNewChildElement ("PropertyGroup"); - setConditionAttribute (*e, config); - e->setAttribute ("Label", "Configuration"); - e->createNewChildElement ("ConfigurationType")->addTextElement (getProjectType()); - e->createNewChildElement ("UseOfMfc")->addTextElement ("false"); - e->createNewChildElement ("WholeProgramOptimization")->addTextElement (config.isLinkTimeOptimisationEnabled() ? "true" - : "false"); - - auto charSet = config.getCharacterSetString(); - - if (charSet.isNotEmpty()) - e->createNewChildElement ("CharacterSet")->addTextElement (charSet); - - if (config.shouldLinkIncremental()) - e->createNewChildElement ("LinkIncremental")->addTextElement ("true"); - - e->createNewChildElement ("PlatformToolset")->addTextElement (owner.getPlatformToolset()); - - addWindowsTargetPlatformToConfig (*e); - - struct IntelLibraryInfo + for (const auto& arch : allArchitectures) { - String libraryKind; - String configString; - }; + auto* e = projectXml.createNewChildElement ("PropertyGroup"); + setConditionAttribute (*e, config, arch); + e->setAttribute ("Label", "Configuration"); + e->createNewChildElement ("ConfigurationType")->addTextElement (getProjectType()); + e->createNewChildElement ("UseOfMfc")->addTextElement ("false"); + e->createNewChildElement ("WholeProgramOptimization")->addTextElement (config.isLinkTimeOptimisationEnabled() ? "true" + : "false"); - for (const auto& info : { IntelLibraryInfo { owner.getIPPLibrary(), "UseIntelIPP" }, - IntelLibraryInfo { owner.getIPP1ALibrary(), "UseIntelIPP1A" }, - IntelLibraryInfo { owner.getMKL1ALibrary(), "UseInteloneMKL" } }) - { - if (info.libraryKind.isNotEmpty()) - e->createNewChildElement (info.configString)->addTextElement (info.libraryKind); + auto charSet = config.getCharacterSetString(); + + if (charSet.isNotEmpty()) + e->createNewChildElement ("CharacterSet")->addTextElement (charSet); + + if (config.shouldLinkIncremental()) + e->createNewChildElement ("LinkIncremental")->addTextElement ("true"); + + e->createNewChildElement ("PlatformToolset")->addTextElement (owner.getPlatformToolset()); + + addWindowsTargetPlatformToConfig (*e); + + struct IntelLibraryInfo + { + String libraryKind; + String configString; + }; + + for (const auto& info : { IntelLibraryInfo { owner.getIPPLibrary(), "UseIntelIPP" }, + IntelLibraryInfo { owner.getIPP1ALibrary(), "UseIntelIPP1A" }, + IntelLibraryInfo { owner.getMKL1ALibrary(), "UseInteloneMKL" } }) + { + if (info.libraryKind.isNotEmpty()) + e->createNewChildElement (info.configString)->addTextElement (info.libraryKind); + } } } @@ -552,47 +735,50 @@ public: for (ConstConfigIterator i (owner); i.next();) { - auto& config = dynamic_cast (*i); + auto& config = *static_cast (&*i); - if (getConfigTargetPath (config).isNotEmpty()) + for (const auto& arch : allArchitectures) { - auto* outdir = props->createNewChildElement ("OutDir"); - setConditionAttribute (*outdir, config); - outdir->addTextElement (build_tools::windowsStylePath (getConfigTargetPath (config)) + "\\"); - } - - { - auto* intdir = props->createNewChildElement ("IntDir"); - setConditionAttribute (*intdir, config); - - auto intermediatesPath = getIntermediatesPath (config); - if (! intermediatesPath.endsWithChar (L'\\')) - intermediatesPath += L'\\'; - - intdir->addTextElement (build_tools::windowsStylePath (intermediatesPath)); - } - - { - auto* targetName = props->createNewChildElement ("TargetName"); - setConditionAttribute (*targetName, config); - targetName->addTextElement (msBuildEscape (config.getOutputFilename ("", false, type))); - } - - { - auto* manifest = props->createNewChildElement ("GenerateManifest"); - setConditionAttribute (*manifest, config); - manifest->addTextElement ("true"); - } - - if (type != SharedCodeTarget) - { - auto librarySearchPaths = getLibrarySearchPaths (config); - - if (! librarySearchPaths.isEmpty()) + if (getConfigTargetPath (config).isNotEmpty()) { - auto* libPath = props->createNewChildElement ("LibraryPath"); - setConditionAttribute (*libPath, config); - libPath->addTextElement ("$(LibraryPath);" + librarySearchPaths.joinIntoString (";")); + auto* outdir = props->createNewChildElement ("OutDir"); + setConditionAttribute (*outdir, config, arch); + outdir->addTextElement (build_tools::windowsStylePath (getConfigTargetPath (config)) + "\\"); + } + + { + auto* intdir = props->createNewChildElement ("IntDir"); + setConditionAttribute (*intdir, config, arch); + + auto intermediatesPath = getIntermediatesPath (config); + if (! intermediatesPath.endsWithChar (L'\\')) + intermediatesPath += L'\\'; + + intdir->addTextElement (build_tools::windowsStylePath (intermediatesPath)); + } + + { + auto* targetName = props->createNewChildElement ("TargetName"); + setConditionAttribute (*targetName, config, arch); + targetName->addTextElement (msBuildEscape (config.getOutputFilename ("", false, type))); + } + + { + auto* manifest = props->createNewChildElement ("GenerateManifest"); + setConditionAttribute (*manifest, config, arch); + manifest->addTextElement ("true"); + } + + if (type != SharedCodeTarget) + { + auto librarySearchPaths = getLibrarySearchPaths (config); + + if (! librarySearchPaths.isEmpty()) + { + auto* libPath = props->createNewChildElement ("LibraryPath"); + setConditionAttribute (*libPath, config, arch); + libPath->addTextElement ("$(LibraryPath);" + librarySearchPaths.joinIntoString (";")); + } } } } @@ -600,191 +786,194 @@ public: for (ConstConfigIterator i (owner); i.next();) { - auto& config = dynamic_cast (*i); + auto& config = *static_cast (&*i); - enum class EscapeQuotes { no, yes }; - - // VS doesn't correctly escape double quotes in preprocessor definitions, so we have - // to add our own layer of escapes - const auto addIncludePathsAndPreprocessorDefinitions = [this, &config] (XmlElement& xml, EscapeQuotes escapeQuotes) + for (const auto& arch : allArchitectures) { - auto includePaths = getOwner().getHeaderSearchPaths (config); - includePaths.add ("%(AdditionalIncludeDirectories)"); - xml.createNewChildElement ("AdditionalIncludeDirectories")->addTextElement (includePaths.joinIntoString (";")); + enum class EscapeQuotes { no, yes }; - const auto preprocessorDefs = getPreprocessorDefs (config, ";") + ";%(PreprocessorDefinitions)"; - const auto preprocessorDefsEscaped = escapeQuotes == EscapeQuotes::yes ? preprocessorDefs.replace ("\"", "\\\"") - : preprocessorDefs; - xml.createNewChildElement ("PreprocessorDefinitions")->addTextElement (preprocessorDefsEscaped); - }; - - bool isDebug = config.isDebug(); - - auto* group = projectXml.createNewChildElement ("ItemDefinitionGroup"); - setConditionAttribute (*group, config); - - { - auto* midl = group->createNewChildElement ("Midl"); - midl->createNewChildElement ("PreprocessorDefinitions")->addTextElement (isDebug ? "_DEBUG;%(PreprocessorDefinitions)" - : "NDEBUG;%(PreprocessorDefinitions)"); - midl->createNewChildElement ("MkTypLibCompatible")->addTextElement ("true"); - midl->createNewChildElement ("SuppressStartupBanner")->addTextElement ("true"); - midl->createNewChildElement ("TargetEnvironment")->addTextElement ("Win32"); - midl->createNewChildElement ("HeaderFileName"); - } - - bool isUsingEditAndContinue = false; - const auto pdbFilename = getOwner().getIntDirFile (config, config.getOutputFilename (".pdb", true, type)); - - { - auto* cl = group->createNewChildElement ("ClCompile"); - - cl->createNewChildElement ("Optimization")->addTextElement (getOptimisationLevelString (config.getOptimisationLevelInt())); - - if (isDebug || config.shouldGenerateDebugSymbols()) + // VS doesn't correctly escape double quotes in preprocessor definitions, so we have + // to add our own layer of escapes + const auto addIncludePathsAndPreprocessorDefinitions = [this, &config] (XmlElement& xml, EscapeQuotes escapeQuotes) { - cl->createNewChildElement ("DebugInformationFormat") - ->addTextElement (config.getDebugInformationFormatString()); + auto includePaths = getOwner().getHeaderSearchPaths (config); + includePaths.add ("%(AdditionalIncludeDirectories)"); + xml.createNewChildElement ("AdditionalIncludeDirectories")->addTextElement (includePaths.joinIntoString (";")); + + const auto preprocessorDefs = getPreprocessorDefs (config, ";") + ";%(PreprocessorDefinitions)"; + const auto preprocessorDefsEscaped = escapeQuotes == EscapeQuotes::yes ? preprocessorDefs.replace ("\"", "\\\"") + : preprocessorDefs; + xml.createNewChildElement ("PreprocessorDefinitions")->addTextElement (preprocessorDefsEscaped); + }; + + bool isDebug = config.isDebug(); + + auto* group = projectXml.createNewChildElement ("ItemDefinitionGroup"); + setConditionAttribute (*group, config, arch); + + { + auto* midl = group->createNewChildElement ("Midl"); + midl->createNewChildElement ("PreprocessorDefinitions")->addTextElement (isDebug ? "_DEBUG;%(PreprocessorDefinitions)" + : "NDEBUG;%(PreprocessorDefinitions)"); + midl->createNewChildElement ("MkTypLibCompatible")->addTextElement ("true"); + midl->createNewChildElement ("SuppressStartupBanner")->addTextElement ("true"); + midl->createNewChildElement ("TargetEnvironment")->addTextElement ("Win32"); + midl->createNewChildElement ("HeaderFileName"); } - addIncludePathsAndPreprocessorDefinitions (*cl, EscapeQuotes::no); + bool isUsingEditAndContinue = false; + const auto pdbFilename = getOwner().getIntDirFile (config, config.getOutputFilename (".pdb", true, type)); - cl->createNewChildElement ("RuntimeLibrary")->addTextElement (config.isUsingRuntimeLibDLL() ? (isDebug ? "MultiThreadedDebugDLL" : "MultiThreadedDLL") - : (isDebug ? "MultiThreadedDebug" : "MultiThreaded")); - cl->createNewChildElement ("RuntimeTypeInfo")->addTextElement ("true"); - cl->createNewChildElement ("PrecompiledHeader")->addTextElement ("NotUsing"); - cl->createNewChildElement ("AssemblerListingLocation")->addTextElement ("$(IntDir)\\"); - cl->createNewChildElement ("ObjectFileName")->addTextElement ("$(IntDir)\\"); - cl->createNewChildElement ("ProgramDataBaseFileName")->addTextElement (pdbFilename); - cl->createNewChildElement ("WarningLevel")->addTextElement ("Level" + String (config.getWarningLevel())); - cl->createNewChildElement ("SuppressStartupBanner")->addTextElement ("true"); - cl->createNewChildElement ("MultiProcessorCompilation")->addTextElement (config.shouldUseMultiProcessorCompilation() ? "true" : "false"); - - if (config.isFastMathEnabled()) - cl->createNewChildElement ("FloatingPointModel")->addTextElement ("Fast"); - - auto extraFlags = getOwner().replacePreprocessorTokens (config, config.getAllCompilerFlagsString()).trim(); - - if (extraFlags.isNotEmpty()) - cl->createNewChildElement ("AdditionalOptions")->addTextElement (extraFlags + " %(AdditionalOptions)"); - - if (config.areWarningsTreatedAsErrors()) - cl->createNewChildElement ("TreatWarningAsError")->addTextElement ("true"); - - auto cppStandard = owner.project.getCppStandardString(); - cl->createNewChildElement ("LanguageStandard")->addTextElement ("stdcpp" + cppStandard); - } - - { - auto* res = group->createNewChildElement ("ResourceCompile"); - addIncludePathsAndPreprocessorDefinitions (*res, EscapeQuotes::yes); - } - - auto externalLibraries = getExternalLibraries (config, getOwner().getExternalLibrariesStringArray()); - auto additionalDependencies = type != SharedCodeTarget && type != LV2Helper && type != VST3Helper && ! externalLibraries.isEmpty() - ? externalLibraries.joinIntoString (";") + ";%(AdditionalDependencies)" - : String(); - - auto librarySearchPaths = config.getLibrarySearchPaths(); - auto additionalLibraryDirs = type != SharedCodeTarget && type != LV2Helper && type != VST3Helper && librarySearchPaths.size() > 0 - ? getOwner().replacePreprocessorTokens (config, librarySearchPaths.joinIntoString (";")) + ";%(AdditionalLibraryDirectories)" - : String(); - - { - auto* link = group->createNewChildElement ("Link"); - link->createNewChildElement ("OutputFile")->addTextElement (getOutputFilePath (config)); - link->createNewChildElement ("SuppressStartupBanner")->addTextElement ("true"); - link->createNewChildElement ("IgnoreSpecificDefaultLibraries")->addTextElement (isDebug ? "libcmt.lib; msvcrt.lib;;%(IgnoreSpecificDefaultLibraries)" - : "%(IgnoreSpecificDefaultLibraries)"); - link->createNewChildElement ("GenerateDebugInformation")->addTextElement ((isDebug || config.shouldGenerateDebugSymbols()) ? "true" : "false"); - link->createNewChildElement ("ProgramDatabaseFile")->addTextElement (pdbFilename); - link->createNewChildElement ("SubSystem")->addTextElement (type == ConsoleApp || type == LV2Helper || type == VST3Helper ? "Console" : "Windows"); - - if (config.getArchitectureString() == "Win32") - link->createNewChildElement ("TargetMachine")->addTextElement ("MachineX86"); - - if (isUsingEditAndContinue) - link->createNewChildElement ("ImageHasSafeExceptionHandlers")->addTextElement ("false"); - - if (! isDebug) { - link->createNewChildElement ("OptimizeReferences")->addTextElement ("true"); - link->createNewChildElement ("EnableCOMDATFolding")->addTextElement ("true"); + auto* cl = group->createNewChildElement ("ClCompile"); + + cl->createNewChildElement ("Optimization")->addTextElement (getOptimisationLevelString (config.getOptimisationLevelInt())); + + if (isDebug || config.shouldGenerateDebugSymbols()) + { + cl->createNewChildElement ("DebugInformationFormat") + ->addTextElement (config.getDebugInformationFormatString()); + } + + addIncludePathsAndPreprocessorDefinitions (*cl, EscapeQuotes::no); + + cl->createNewChildElement ("RuntimeLibrary")->addTextElement (config.isUsingRuntimeLibDLL() ? (isDebug ? "MultiThreadedDebugDLL" : "MultiThreadedDLL") + : (isDebug ? "MultiThreadedDebug" : "MultiThreaded")); + cl->createNewChildElement ("RuntimeTypeInfo")->addTextElement ("true"); + cl->createNewChildElement ("PrecompiledHeader")->addTextElement ("NotUsing"); + cl->createNewChildElement ("AssemblerListingLocation")->addTextElement ("$(IntDir)\\"); + cl->createNewChildElement ("ObjectFileName")->addTextElement ("$(IntDir)\\"); + cl->createNewChildElement ("ProgramDataBaseFileName")->addTextElement (pdbFilename); + cl->createNewChildElement ("WarningLevel")->addTextElement ("Level" + String (config.getWarningLevel())); + cl->createNewChildElement ("SuppressStartupBanner")->addTextElement ("true"); + cl->createNewChildElement ("MultiProcessorCompilation")->addTextElement (config.shouldUseMultiProcessorCompilation() ? "true" : "false"); + + if (config.isFastMathEnabled()) + cl->createNewChildElement ("FloatingPointModel")->addTextElement ("Fast"); + + auto extraFlags = getOwner().replacePreprocessorTokens (config, config.getAllCompilerFlagsString()).trim(); + + if (extraFlags.isNotEmpty()) + cl->createNewChildElement ("AdditionalOptions")->addTextElement (extraFlags + " %(AdditionalOptions)"); + + if (config.areWarningsTreatedAsErrors()) + cl->createNewChildElement ("TreatWarningAsError")->addTextElement ("true"); + + auto cppStandard = owner.project.getCppStandardString(); + cl->createNewChildElement ("LanguageStandard")->addTextElement ("stdcpp" + cppStandard); } - if (additionalLibraryDirs.isNotEmpty()) - link->createNewChildElement ("AdditionalLibraryDirectories")->addTextElement (additionalLibraryDirs); - - link->createNewChildElement ("LargeAddressAware")->addTextElement ("true"); - - if (config.isLinkTimeOptimisationEnabled()) - link->createNewChildElement ("LinkTimeCodeGeneration")->addTextElement ("UseLinkTimeCodeGeneration"); - - if (additionalDependencies.isNotEmpty()) - link->createNewChildElement ("AdditionalDependencies")->addTextElement (additionalDependencies); - - auto extraLinkerOptions = config.getAllLinkerFlagsString(); - if (extraLinkerOptions.isNotEmpty()) - link->createNewChildElement ("AdditionalOptions")->addTextElement (getOwner().replacePreprocessorTokens (config, extraLinkerOptions).trim() - + " %(AdditionalOptions)"); - - auto delayLoadedDLLs = getOwner().msvcDelayLoadedDLLs; - if (delayLoadedDLLs.isNotEmpty()) - link->createNewChildElement ("DelayLoadDLLs")->addTextElement (delayLoadedDLLs); - - auto moduleDefinitionsFile = getModuleDefinitions (config); - if (moduleDefinitionsFile.isNotEmpty()) - link->createNewChildElement ("ModuleDefinitionFile") - ->addTextElement (moduleDefinitionsFile); - } - - { - auto* bsc = group->createNewChildElement ("Bscmake"); - bsc->createNewChildElement ("SuppressStartupBanner")->addTextElement ("true"); - bsc->createNewChildElement ("OutputFile")->addTextElement (getOwner().getIntDirFile (config, config.getOutputFilename (".bsc", true, type))); - } - - if (type != SharedCodeTarget && type != LV2Helper && type != VST3Helper) - { - auto* lib = group->createNewChildElement ("Lib"); - - if (additionalDependencies.isNotEmpty()) - lib->createNewChildElement ("AdditionalDependencies")->addTextElement (additionalDependencies); - - if (additionalLibraryDirs.isNotEmpty()) - lib->createNewChildElement ("AdditionalLibraryDirectories")->addTextElement (additionalLibraryDirs); - } - - if (auto manifestFile = getOwner().getManifestPath(); manifestFile.getRoot() != build_tools::RelativePath::unknown || type == VST3Helper) - { - auto* bsc = group->createNewChildElement ("Manifest"); - auto* additional = bsc->createNewChildElement ("AdditionalManifestFiles"); - - if (manifestFile.getRoot() != build_tools::RelativePath::unknown) { - additional->addTextElement (manifestFile.rebased (getOwner().getProject().getFile().getParentDirectory(), - getOwner().getTargetFolder(), - build_tools::RelativePath::buildTargetFolder).toWindowsStyle()); + auto* res = group->createNewChildElement ("ResourceCompile"); + addIncludePathsAndPreprocessorDefinitions (*res, EscapeQuotes::yes); } + + auto externalLibraries = getExternalLibraries (config, getOwner().getExternalLibrariesStringArray()); + auto additionalDependencies = type != SharedCodeTarget && type != LV2Helper && type != VST3Helper && ! externalLibraries.isEmpty() + ? externalLibraries.joinIntoString (";") + ";%(AdditionalDependencies)" + : String(); + + auto librarySearchPaths = config.getLibrarySearchPaths(); + auto additionalLibraryDirs = type != SharedCodeTarget && type != LV2Helper && type != VST3Helper && librarySearchPaths.size() > 0 + ? getOwner().replacePreprocessorTokens (config, librarySearchPaths.joinIntoString (";")) + ";%(AdditionalLibraryDirectories)" + : String(); + + { + auto* link = group->createNewChildElement ("Link"); + link->createNewChildElement ("OutputFile")->addTextElement (getOutputFilePath (config)); + link->createNewChildElement ("SuppressStartupBanner")->addTextElement ("true"); + link->createNewChildElement ("IgnoreSpecificDefaultLibraries")->addTextElement (isDebug ? "libcmt.lib; msvcrt.lib;;%(IgnoreSpecificDefaultLibraries)" + : "%(IgnoreSpecificDefaultLibraries)"); + link->createNewChildElement ("GenerateDebugInformation")->addTextElement ((isDebug || config.shouldGenerateDebugSymbols()) ? "true" : "false"); + link->createNewChildElement ("ProgramDatabaseFile")->addTextElement (pdbFilename); + link->createNewChildElement ("SubSystem")->addTextElement (type == ConsoleApp || type == LV2Helper || type == VST3Helper ? "Console" : "Windows"); + + if (arch == Architecture::win32) + link->createNewChildElement ("TargetMachine")->addTextElement ("MachineX86"); + + if (isUsingEditAndContinue) + link->createNewChildElement ("ImageHasSafeExceptionHandlers")->addTextElement ("false"); + + if (! isDebug) + { + link->createNewChildElement ("OptimizeReferences")->addTextElement ("true"); + link->createNewChildElement ("EnableCOMDATFolding")->addTextElement ("true"); + } + + if (additionalLibraryDirs.isNotEmpty()) + link->createNewChildElement ("AdditionalLibraryDirectories")->addTextElement (additionalLibraryDirs); + + link->createNewChildElement ("LargeAddressAware")->addTextElement ("true"); + + if (config.isLinkTimeOptimisationEnabled()) + link->createNewChildElement ("LinkTimeCodeGeneration")->addTextElement ("UseLinkTimeCodeGeneration"); + + if (additionalDependencies.isNotEmpty()) + link->createNewChildElement ("AdditionalDependencies")->addTextElement (additionalDependencies); + + auto extraLinkerOptions = config.getAllLinkerFlagsString(); + if (extraLinkerOptions.isNotEmpty()) + link->createNewChildElement ("AdditionalOptions")->addTextElement (getOwner().replacePreprocessorTokens (config, extraLinkerOptions).trim() + + " %(AdditionalOptions)"); + + auto delayLoadedDLLs = getOwner().msvcDelayLoadedDLLs; + if (delayLoadedDLLs.isNotEmpty()) + link->createNewChildElement ("DelayLoadDLLs")->addTextElement (delayLoadedDLLs); + + auto moduleDefinitionsFile = getModuleDefinitions (config); + if (moduleDefinitionsFile.isNotEmpty()) + link->createNewChildElement ("ModuleDefinitionFile") + ->addTextElement (moduleDefinitionsFile); + } + + { + auto* bsc = group->createNewChildElement ("Bscmake"); + bsc->createNewChildElement ("SuppressStartupBanner")->addTextElement ("true"); + bsc->createNewChildElement ("OutputFile")->addTextElement (getOwner().getIntDirFile (config, config.getOutputFilename (".bsc", true, type))); + } + + if (type != SharedCodeTarget && type != LV2Helper && type != VST3Helper) + { + auto* lib = group->createNewChildElement ("Lib"); + + if (additionalDependencies.isNotEmpty()) + lib->createNewChildElement ("AdditionalDependencies")->addTextElement (additionalDependencies); + + if (additionalLibraryDirs.isNotEmpty()) + lib->createNewChildElement ("AdditionalLibraryDirectories")->addTextElement (additionalLibraryDirs); + } + + if (auto manifestFile = getOwner().getManifestPath(); manifestFile.getRoot() != build_tools::RelativePath::unknown || type == VST3Helper) + { + auto* bsc = group->createNewChildElement ("Manifest"); + auto* additional = bsc->createNewChildElement ("AdditionalManifestFiles"); + + if (manifestFile.getRoot() != build_tools::RelativePath::unknown) + { + additional->addTextElement (manifestFile.rebased (getOwner().getProject().getFile().getParentDirectory(), + getOwner().getTargetFolder(), + build_tools::RelativePath::buildTargetFolder).toWindowsStyle()); + } + } + + if (getTargetFileType() == staticLibrary && arch == Architecture::win32) + { + auto* lib = group->createNewChildElement ("Lib"); + lib->createNewChildElement ("TargetMachine")->addTextElement ("MachineX86"); + } + + auto preBuild = getPreBuildSteps (config, arch); + if (preBuild.isNotEmpty()) + group->createNewChildElement ("PreBuildEvent") + ->createNewChildElement ("Command") + ->addTextElement (preBuild); + + auto postBuild = getPostBuildSteps (config, arch); + if (postBuild.isNotEmpty()) + group->createNewChildElement ("PostBuildEvent") + ->createNewChildElement ("Command") + ->addTextElement (postBuild); } - - if (getTargetFileType() == staticLibrary && config.getArchitectureString() == "Win32") - { - auto* lib = group->createNewChildElement ("Lib"); - lib->createNewChildElement ("TargetMachine")->addTextElement ("MachineX86"); - } - - auto preBuild = getPreBuildSteps (config); - if (preBuild.isNotEmpty()) - group->createNewChildElement ("PreBuildEvent") - ->createNewChildElement ("Command") - ->addTextElement (preBuild); - - auto postBuild = getPostBuildSteps (config); - if (postBuild.isNotEmpty()) - group->createNewChildElement ("PostBuildEvent") - ->createNewChildElement ("Command") - ->addTextElement (postBuild); } std::unique_ptr otherFilesGroup (new XmlElement ("ItemGroup")); @@ -878,11 +1067,15 @@ public: } //============================================================================== - static void setSourceFilePCHSettings (XmlElement& element, const File& pchFile, const String& option, const BuildConfiguration& config) + static void setSourceFilePCHSettings (XmlElement& element, + const File& pchFile, + const String& option, + const BuildConfiguration& config, + Architecture arch) { - auto setConfigConditionAttribute = [&config] (XmlElement* elementToSet) -> XmlElement* + auto setConfigConditionAttribute = [&config, arch] (XmlElement* elementToSet) -> XmlElement* { - setConditionAttribute (*elementToSet, config); + setConditionAttribute (*elementToSet, config, arch); return elementToSet; }; @@ -894,39 +1087,44 @@ public: void writePrecompiledHeaderFiles (XmlElement& cpps) const { - for (ConstConfigIterator config (owner); config.next();) + for (ConstConfigIterator i (owner); i.next();) { - if (config->shouldUsePrecompiledHeaderFile()) + if (! i->shouldUsePrecompiledHeaderFile()) + continue; + + auto& config = *static_cast (&*i); + + for (const auto& arch : config.getArchitectures()) { - auto pchFileContent = config->getPrecompiledHeaderFileContent(); + auto pchFileContent = config.getPrecompiledHeaderFileContent(); - if (pchFileContent.isNotEmpty()) + if (pchFileContent.isEmpty()) + continue; + + auto pchFile = owner.getTargetFolder().getChildFile (config.getPrecompiledHeaderFilename()).withFileExtension (".h"); + + build_tools::writeStreamToFile (pchFile, [&] (MemoryOutputStream& mo) { - auto pchFile = owner.getTargetFolder().getChildFile (config->getPrecompiledHeaderFilename()).withFileExtension (".h"); + mo << pchFileContent; + }); - build_tools::writeStreamToFile (pchFile, [&] (MemoryOutputStream& mo) - { - mo << pchFileContent; - }); + auto pchSourceFile = pchFile.withFileExtension (".cpp"); - auto pchSourceFile = pchFile.withFileExtension (".cpp"); + build_tools::writeStreamToFile (pchSourceFile, [this] (MemoryOutputStream& mo) + { + mo.setNewLineString (owner.getNewLineString()); - build_tools::writeStreamToFile (pchSourceFile, [this] (MemoryOutputStream& mo) - { - mo.setNewLineString (owner.getNewLineString()); + writeAutoGenWarningComment (mo); - writeAutoGenWarningComment (mo); + mo << " This is an empty source file generated by JUCE required for Visual Studio PCH." << newLine + << newLine + << "*/" << newLine + << newLine; + }); - mo << " This is an empty source file generated by JUCE required for Visual Studio PCH." << newLine - << newLine - << "*/" << newLine - << newLine; - }); - - auto* pchSourceElement = cpps.createNewChildElement ("ClCompile"); - pchSourceElement->setAttribute ("Include", prependDot (pchSourceFile.getFileName())); - setSourceFilePCHSettings (*pchSourceElement, pchFile, "Create", *config); - } + auto* pchSourceElement = cpps.createNewChildElement ("ClCompile"); + pchSourceElement->setAttribute ("Include", prependDot (pchSourceFile.getFileName())); + setSourceFilePCHSettings (*pchSourceElement, pchFile, "Create", config, arch); } } } @@ -974,12 +1172,17 @@ public: { for (ConstConfigIterator i (owner); i.next();) { - if (i->shouldUsePrecompiledHeaderFile()) - { - auto pchFile = owner.getTargetFolder().getChildFile (i->getPrecompiledHeaderFilename()).withFileExtension (".h"); + auto& config = *static_cast (&*i); - if (pchFile.existsAsFile()) - setSourceFilePCHSettings (*e, pchFile, "Use", *i); + if (config.shouldUsePrecompiledHeaderFile()) + { + for (const auto& arch : config.getArchitectures()) + { + auto pchFile = owner.getTargetFolder().getChildFile (i->getPrecompiledHeaderFilename()).withFileExtension (".h"); + + if (pchFile.existsAsFile()) + setSourceFilePCHSettings (*e, pchFile, "Use", *i, arch); + } } } } @@ -1000,10 +1203,10 @@ public: } } - static void setConditionAttribute (XmlElement& xml, const BuildConfiguration& config) + static void setConditionAttribute (XmlElement& xml, const BuildConfiguration& config, Architecture arch) { - auto& msvcConfig = dynamic_cast (config); - xml.setAttribute ("Condition", "'$(Configuration)|$(Platform)'=='" + msvcConfig.createMSVCConfigName() + "'"); + auto& msvcConfig = *static_cast (&config); + xml.setAttribute ("Condition", "'$(Configuration)|$(Platform)'=='" + msvcConfig.createMSVCConfigName (arch) + "'"); } //============================================================================== @@ -1080,7 +1283,7 @@ public: for (int i = 0; i < getOwner().getAllGroups().size(); ++i) { - auto& group = getOwner().getAllGroups().getReference(i); + auto& group = getOwner().getAllGroups().getReference (i); if (group.getNumChildren() > 0) addFilesToFilter (group, group.getName(), *cpps, *headers, *otherFilesGroup, *groupsXml); @@ -1255,7 +1458,7 @@ public: return aaxSdk.getChildFile ("Utilities").getChildFile ("PlugIn.ico"); } - String getExtraPostBuildSteps (const MSVCBuildConfiguration& config) const + String getExtraPostBuildSteps (const MSVCBuildConfiguration& config, Architecture arch) const { const auto copyBuildOutputIntoBundle = [&] (const StringArray& segments) { @@ -1282,7 +1485,7 @@ public: const auto bundleScript = aaxSdk.getChildFile ("Utilities").getChildFile ("CreatePackage.bat"); const auto iconFilePath = getAAXIconFile(); - const auto segments = getAaxBundleStructure (config); + const auto segments = getAaxBundleStructure (config, arch); const auto pkgScript = copyBuildOutputIntoBundle (segments); @@ -1295,7 +1498,7 @@ public: + String (" ") + createRebasedPath (iconFilePath); - const auto copyScript = copyBundleToInstallDirectory (segments, config.getAAXBinaryLocationString()); + const auto copyScript = copyBundleToInstallDirectory (segments, config.getBinaryPath (Ids::aaxBinaryLocation, arch)); return pkgScript + fixScript + copyScript; } @@ -1310,7 +1513,7 @@ public: if (config.isPluginBinaryCopyStepEnabled()) { - auto copyLocation = config.getUnityPluginBinaryLocationString(); + auto copyLocation = config.getBinaryPath (Ids::unityPluginBinaryLocation, arch); pkgScript += "\r\ncopy /Y \"$(OutDir)$(TargetFileName)\" " + String (copyLocation + "\\$(TargetFileName)").quoted(); pkgScript += "\r\ncopy /Y " + String ("$(OutDir)" + config.project.getUnityScriptName()).quoted() + " " + String (copyLocation + "\\" + config.project.getUnityScriptName()).quoted(); @@ -1335,7 +1538,7 @@ public: + writerTarget->getBinaryNameWithSuffix (config); const auto copyStep = "xcopy /E /H /I /K /R /Y \"$(OutDir)\" \"" - + config.getLV2BinaryLocationString() + + config.getBinaryPath (Ids::lv2BinaryLocation, arch) + '\\' + config.getTargetBinaryNameString() + ".lv2\"\r\n"; @@ -1347,7 +1550,7 @@ public: if (type == VST3PlugIn) { - const auto segments = getVst3BundleStructure (config); + const auto segments = getVst3BundleStructure (config, arch); const auto manifestScript = [&]() -> String { @@ -1384,18 +1587,18 @@ public: }(); const auto pkgScript = copyBuildOutputIntoBundle (segments); - const auto copyScript = copyBundleToInstallDirectory (segments, config.getVST3BinaryLocationString()); + const auto copyScript = copyBundleToInstallDirectory (segments, config.getBinaryPath (Ids::vst3BinaryLocation, arch)); return pkgScript + manifestScript + copyScript; } if (type == VSTPlugIn && config.isPluginBinaryCopyStepEnabled()) - return "copy /Y \"$(OutDir)$(TargetFileName)\" \"" + config.getVSTBinaryLocationString() + "\\$(TargetFileName)\""; + return "copy /Y \"$(OutDir)$(TargetFileName)\" \"" + config.getBinaryPath (Ids::vstBinaryLocation, arch) + "\\$(TargetFileName)\""; return {}; } - String getExtraPreBuildSteps (const MSVCBuildConfiguration& config) const + String getExtraPreBuildSteps (const MSVCBuildConfiguration& config, Architecture arch) const { const auto createBundleStructure = [&] (const StringArray& segments) { @@ -1412,26 +1615,26 @@ public: }; if (type == AAXPlugIn) - return createBundleStructure (getAaxBundleStructure (config)); + return createBundleStructure (getAaxBundleStructure (config, arch)); if (type == VST3PlugIn) - return createBundleStructure (getVst3BundleStructure (config)); + return createBundleStructure (getVst3BundleStructure (config, arch)); return {}; } - String getPostBuildSteps (const MSVCBuildConfiguration& config) const + String getPostBuildSteps (const MSVCBuildConfiguration& config, Architecture arch) const { auto postBuild = config.getPostbuildCommandString().replace ("\n", "\r\n");; - auto extraPostBuild = getExtraPostBuildSteps (config); + auto extraPostBuild = getExtraPostBuildSteps (config, arch); return postBuild + String (postBuild.isNotEmpty() && extraPostBuild.isNotEmpty() ? "\r\n" : "") + extraPostBuild; } - String getPreBuildSteps (const MSVCBuildConfiguration& config) const + String getPreBuildSteps (const MSVCBuildConfiguration& config, Architecture arch) const { auto preBuild = config.getPrebuildCommandString().replace ("\n", "\r\n");; - auto extraPreBuild = getExtraPreBuildSteps (config); + auto extraPreBuild = getExtraPreBuildSteps (config, arch); return preBuild + String (preBuild.isNotEmpty() && extraPreBuild.isNotEmpty() ? "\r\n" : "") + extraPreBuild; } @@ -1513,24 +1716,31 @@ public: } protected: - StringArray getAaxBundleStructure (const MSVCBuildConfiguration& config) const + StringArray getAaxBundleStructure (const MSVCBuildConfiguration& config, Architecture arch) const { const auto dllName = config.getOutputFilename (".aaxplugin", false, type); - return { dllName, "Contents", config.getArchitectureString(), dllName }; + return { dllName, "Contents", toString (arch), dllName }; } - StringArray getVst3BundleStructure (const MSVCBuildConfiguration& config) const + StringArray getVst3BundleStructure (const MSVCBuildConfiguration& config, Architecture arch) const { - static const std::map suffixes + // These suffixes are defined in the VST3 SDK docs + const auto suffix = std::invoke ([&]() -> String { - { "Win32", "x86" }, - { "x64", "x86_64" }, - }; + switch (arch) + { + case Architecture::win32: return "x86"; + case Architecture::win64: return "x86_64"; + case Architecture::arm64: return "arm64"; + case Architecture::arm64ec: return "arm64ec"; + } - const auto iter = suffixes.find (config.getArchitectureString()); + jassertfalse; + return {}; + }); const auto dllName = config.getOutputFilename (".vst3", false, type); - return { dllName, "Contents", iter != suffixes.cend() ? iter->second + "-win" : "win", dllName }; + return { dllName, "Contents", suffix + "-win", dllName }; } const MSVCProjectExporterBase& owner; @@ -1667,7 +1877,7 @@ public: msvcExtraPreprocessorDefs.set ("_CRT_SECURE_NO_WARNINGS", ""); if (type.isCommandLineApp()) - msvcExtraPreprocessorDefs.set("_CONSOLE", ""); + msvcExtraPreprocessorDefs.set ("_CONSOLE", ""); callForAllSupportedTargets ([this] (build_tools::ProjectType::Target::Type targetType) { @@ -1849,23 +2059,41 @@ protected: for (ConstConfigIterator i (*this); i.next();) { - auto& config = dynamic_cast (*i); - auto configName = config.createMSVCConfigName(); - out << "\t\t" << configName << " = " << configName << newLine; + auto& config = *static_cast (&*i); + + for (const auto& arch : config.getArchitectures()) + { + auto configName = config.createMSVCConfigName (arch); + out << "\t\t" << configName << " = " << configName << newLine; + } } out << "\tEndGlobalSection" << newLine << "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution" << newLine; + const auto allArchitectures = getAllActiveArchitectures(); + for (auto& target : targets) + { for (ConstConfigIterator i (*this); i.next();) { - auto& config = dynamic_cast (*i); - auto configName = config.createMSVCConfigName(); + auto& config = *static_cast (&*i); - for (auto& suffix : { "ActiveCfg", "Build.0" }) - out << "\t\t" << target->getProjectGuid() << "." << configName << "." << suffix << " = " << configName << newLine; + // Add a configuration for all projects but only mark the desired to be built. + // We have to do this as VS will automatically add the entry anyway. + for (const auto& arch : allArchitectures) + { + auto configName = config.createMSVCConfigName (arch); + + out << "\t\t" << target->getProjectGuid() << "." << configName << "." << "ActiveCfg" << " = " << configName << newLine; + + const auto shouldBuild = config.getArchitectures().contains (arch); + + if (shouldBuild) + out << "\t\t" << target->getProjectGuid() << "." << configName << "." << "Build.0" << " = " << configName << newLine; + } } + } out << "\tEndGlobalSection" << newLine << "\tGlobalSection(SolutionProperties) = preSolution" << newLine From 59bd0702912beb5ec1032b99590b0ebc0bfae134 Mon Sep 17 00:00:00 2001 From: Oliver James Date: Wed, 16 Oct 2024 14:06:28 +0100 Subject: [PATCH 020/557] Projucer: Disable AAX builds for Windows ARM --- .../Source/ProjectSaving/jucer_ProjectExport_MSVC.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index 18fc4b4974..c371155ea6 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -337,6 +337,12 @@ public: bool isFastMathEnabled() const { return fastMathValue.get(); } bool isPluginBinaryCopyStepEnabled() const { return pluginBinaryCopyStepValue.get(); } + static bool shouldBuildTarget (build_tools::ProjectType::Target::Type targetType, Architecture arch) + { + return targetType != build_tools::ProjectType::Target::AAXPlugIn + || (arch != Architecture::arm64 && arch != Architecture::arm64ec); + } + //============================================================================== String createMSVCConfigName (Architecture arch) const { @@ -2087,7 +2093,7 @@ protected: out << "\t\t" << target->getProjectGuid() << "." << configName << "." << "ActiveCfg" << " = " << configName << newLine; - const auto shouldBuild = config.getArchitectures().contains (arch); + const auto shouldBuild = config.shouldBuildTarget (target->type, arch) && config.getArchitectures().contains (arch); if (shouldBuild) out << "\t\t" << target->getProjectGuid() << "." << configName << "." << "Build.0" << " = " << configName << newLine; From 2f3dd44f3366e259356454d13bb0bc1db9e90377 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Tue, 22 Oct 2024 15:46:58 +0100 Subject: [PATCH 021/557] Projucer: Add arm64 warning on Windows --- .../Projucer/Source/Project/jucer_Project.h | 5 +++- .../ProjectSaving/jucer_ProjectExport_MSVC.h | 28 ++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/extras/Projucer/Source/Project/jucer_Project.h b/extras/Projucer/Source/Project/jucer_Project.h index a3ce2124bc..96d60a31de 100644 --- a/extras/Projucer/Source/Project/jucer_Project.h +++ b/extras/Projucer/Source/Project/jucer_Project.h @@ -60,6 +60,7 @@ namespace ProjectMessages DECLARE_ID (manufacturerCodeInvalid); DECLARE_ID (deprecatedExporter); DECLARE_ID (unsupportedArm32Config); + DECLARE_ID (arm64Warning); DECLARE_ID (notification); DECLARE_ID (warning); @@ -74,7 +75,7 @@ namespace ProjectMessages static Identifier warnings[] = { Ids::cppStandard, Ids::moduleNotFound, Ids::jucePath, Ids::jucerFileModified, Ids::missingModuleDependencies, Ids::oldProjucer, Ids::pluginCodeInvalid, Ids::manufacturerCodeInvalid, - Ids::deprecatedExporter, Ids::unsupportedArm32Config }; + Ids::deprecatedExporter, Ids::unsupportedArm32Config, Ids::arm64Warning }; if (std::find (std::begin (warnings), std::end (warnings), message) != std::end (warnings)) return Ids::warning; @@ -101,6 +102,7 @@ namespace ProjectMessages if (message == Ids::manufacturerCodeInvalid) return "Invalid Manufacturer Code"; if (message == Ids::deprecatedExporter) return "Deprecated Exporter"; if (message == Ids::unsupportedArm32Config) return "Unsupported Architecture"; + if (message == Ids::arm64Warning) return "Prefer arm64ec over arm64"; jassertfalse; return {}; @@ -119,6 +121,7 @@ namespace ProjectMessages if (message == Ids::manufacturerCodeInvalid) return "The manufacturer code should be exactly four characters in length."; if (message == Ids::deprecatedExporter) return "The project includes a deprecated exporter."; if (message == Ids::unsupportedArm32Config) return "The project includes a Visual Studio configuration that uses the 32-bit Arm architecture, which is no longer supported. This configuration has been hidden, and will be removed on save."; + if (message == Ids::arm64Warning) return "For software where interoperability is a concern (such as plugins and hosts), arm64ec will provide the best compatibility with existing x64 software"; jassertfalse; return {}; diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index c371155ea6..eff607f49a 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -243,7 +243,8 @@ public: } //============================================================================== - class MSVCBuildConfiguration final : public BuildConfiguration + class MSVCBuildConfiguration final : public BuildConfiguration, + private ValueTree::Listener { public: MSVCBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e) @@ -285,6 +286,31 @@ public: } optimisationLevelValue.setDefault (isDebug() ? optimisationOff : optimiseFull); + + config.addListener (this); + } + + ~MSVCBuildConfiguration() override + { + config.removeListener (this); + } + + void valueTreePropertyChanged (ValueTree&, const Identifier& property) override + { + if (property != Ids::winArchitecture) + return; + + project.removeProjectMessage (ProjectMessages::Ids::arm64Warning); + + const auto selectedArchs = architectureTypeValue.get(); + + if (! selectedArchs.getArray()->contains (toString (Architecture::arm64))) + return; + + if (selectedArchs.getArray()->contains (toString (Architecture::arm64ec))) + return; + + project.addProjectMessage (ProjectMessages::Ids::arm64Warning, {}); } String getBinaryPath (const Identifier& id, Architecture arch) const From b9c6f7833bce19f5f915c82a5eb5b1853ab6665c Mon Sep 17 00:00:00 2001 From: Oliver James Date: Thu, 24 Oct 2024 15:03:06 +0100 Subject: [PATCH 022/557] Projucer: Implement VST3 cross-platform manifest generation support This enables the generation of VST3 manifests across platforms that support it. For instance, Windows ARM64 systems can now generate x64 manifests. --- .../ProjectSaving/jucer_ProjectExport_MSVC.h | 386 ++++++++++++++++-- 1 file changed, 346 insertions(+), 40 deletions(-) diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index eff607f49a..9dbc75521a 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -53,6 +53,193 @@ inline StringArray msBuildEscape (StringArray range) return range; } +class MSVCScriptBuilder +{ +public: + struct StringOrBuilder + { + StringOrBuilder() = default; + StringOrBuilder (const String& s) : value (s) {} + StringOrBuilder (const char* s) : StringOrBuilder (String { s }) {} + StringOrBuilder (const MSVCScriptBuilder& sb) : StringOrBuilder (sb.build()) {} + + bool isNotEmpty() const { return value.isNotEmpty(); } + + String value; + }; + + MSVCScriptBuilder& exit (int code) + { + script << "exit /b " << code; + script << newLine; + return *this; + } + + MSVCScriptBuilder& deleteFile (const StringOrBuilder& path) + { + script << "del /s /q " << path.value; + script << newLine; + return *this; + } + + MSVCScriptBuilder& mkdir (const StringOrBuilder& path) + { + script << "mkdir " << path.value; + script << newLine; + return *this; + } + + MSVCScriptBuilder& delay (int timeSeconds) + { + script << "timeout /t " << String { timeSeconds } << " /nobreak"; + script << newLine; + return *this; + } + + MSVCScriptBuilder& warning (const String& message) + { + script << "echo : Warning: " + message; + script << newLine; + return *this; + } + + MSVCScriptBuilder& info (const String& message) + { + script << "echo : Info: " << message; + script << newLine; + return *this; + } + + MSVCScriptBuilder& error (const String& message) + { + script << "echo : Error: " << message; + script << newLine; + return *this; + } + + MSVCScriptBuilder& run (const StringOrBuilder& command, bool echo = false) + { + if (echo) + script << "echo \"running " << command.value << "\"" << newLine; + + script << command.value; + script << newLine; + return *this; + } + + template + MSVCScriptBuilder& set (const String& name, T value) + { + script << "set " << name << "=" << value; + script << newLine; + return *this; + } + + MSVCScriptBuilder& runAndCheck (const StringOrBuilder& command, + const StringOrBuilder& success, + const StringOrBuilder& failed = {}) + { + run (command.value); + ifelse ("%ERRORLEVEL% equ 0", success.value, failed.value); + return *this; + } + + MSVCScriptBuilder& ifAllConditionsTrue (const StringArray& conditions, const StringOrBuilder& left) + { + jassert (left.isNotEmpty()); + + for (const auto& string : conditions) + script << "if " << string << " "; + + script << "(" << newLine << left.value << ")"; + script << newLine; + return *this; + } + + MSVCScriptBuilder& ifelse (const String& expr, + const StringOrBuilder& left, + const StringOrBuilder& right = {}) + { + jassert (left.isNotEmpty()); + + script << "if " << expr << " (" << newLine << left.value; + + if (right.isNotEmpty()) + script << ") else (" << newLine << right.value << ")"; + else + script << ")"; + + script << newLine; + return *this; + } + + MSVCScriptBuilder& append (const StringOrBuilder& string) + { + script << string.value << newLine; + return *this; + } + + MSVCScriptBuilder& labelledSection (const String& name, const StringOrBuilder& body) + { + script << ":" << name << newLine << body.value << newLine; + return *this; + } + + MSVCScriptBuilder& call (const String& label) + { + script << "call :" << label << newLine; + return *this; + } + + MSVCScriptBuilder& jump (const String& label) + { + script << "goto :" << label << newLine; + return *this; + } + + String build() const + { + MemoryOutputStream stream; + build (stream); + return stream.toUTF8(); + } + +private: + void build (OutputStream& stream) const + { + const auto genTab = [] (int depth, const int tabWidth = 4) + { + return String{}.paddedLeft (' ', depth * tabWidth); + }; + + int depth = 0; + auto lines = StringArray::fromLines (script); + + for (auto [lineIndex, line] : enumerate (lines)) + { + const auto trimmed = line.trim(); + const auto enter = trimmed.endsWith ("("); + const auto leave = trimmed.startsWith (")"); + + if (leave && depth > 0) + depth--; + + if (trimmed.isNotEmpty()) + { + stream << genTab (depth) << trimmed; + + if (lineIndex < lines.size() - 1) + stream << newLine; + } + + if (enter) + depth++; + } + } + + String script; +}; + enum class Architecture { win32, @@ -61,7 +248,7 @@ enum class Architecture arm64ec }; -static String toString (Architecture arch) +static String getArchitectureValueString (Architecture arch) { switch (arch) { @@ -84,7 +271,7 @@ static std::optional architectureTypeFromString (const String& str for (auto value : values) { - if (toString (value) == string) + if (getArchitectureValueString (value) == string) return value; } @@ -92,6 +279,34 @@ static std::optional architectureTypeFromString (const String& str return {}; } +static String getVisualStudioArchitectureId (Architecture architecture) +{ + switch (architecture) + { + case Architecture::win32: return "x86"; + case Architecture::win64: return "AMD64"; + case Architecture::arm64: return "ARM64"; + case Architecture::arm64ec: return "ARM64"; + } + + jassertfalse; + return ""; +} + +static String getVisualStudioPlatformId (Architecture architecture) +{ + switch (architecture) + { + case Architecture::win32: return "x86"; + case Architecture::win64: return "x64"; + case Architecture::arm64: return "ARM64"; + case Architecture::arm64ec: return "ARM64EC"; + } + + jassertfalse; + return ""; +} + //============================================================================== class MSVCProjectExporterBase : public ProjectExporter { @@ -259,7 +474,7 @@ public: multiProcessorCompilationValue (config, Ids::multiProcessorCompilation, getUndoManager(), true), intermediatesPathValue (config, Ids::intermediatesPath, getUndoManager()), characterSetValue (config, Ids::characterSet, getUndoManager()), - architectureTypeValue (config, Ids::winArchitecture, getUndoManager(), Array { toString (Architecture::win64) }, ","), + architectureTypeValue (config, Ids::winArchitecture, getUndoManager(), Array { getArchitectureValueString (Architecture::win64) }, ","), fastMathValue (config, Ids::fastMath, getUndoManager()), debugInformationFormatValue (config, Ids::debugInformationFormat, getUndoManager(), isDebug() ? "ProgramDatabase" : "None"), pluginBinaryCopyStepValue (config, Ids::enablePluginBinaryCopyStep, getUndoManager(), false), @@ -304,10 +519,10 @@ public: const auto selectedArchs = architectureTypeValue.get(); - if (! selectedArchs.getArray()->contains (toString (Architecture::arm64))) + if (! selectedArchs.getArray()->contains (getArchitectureValueString (Architecture::arm64))) return; - if (selectedArchs.getArray()->contains (toString (Architecture::arm64ec))) + if (selectedArchs.getArray()->contains (getArchitectureValueString (Architecture::arm64ec))) return; project.addProjectMessage (ProjectMessages::Ids::arm64Warning, {}); @@ -372,7 +587,7 @@ public: //============================================================================== String createMSVCConfigName (Architecture arch) const { - return getName() + "|" + toString (arch); + return getName() + "|" + getArchitectureValueString (arch); } String getOutputFilename (const String& suffix, @@ -410,8 +625,8 @@ public: for (const auto& arch : architectureList) { - architectureListAsStrings.add (toString (arch)); - architectureListAsVars.add (toString (arch)); + architectureListAsStrings.add (getArchitectureValueString (arch)); + architectureListAsVars.add (getArchitectureValueString (arch)); } props.add (new MultiChoicePropertyComponent (architectureTypeValue, "Architecture", architectureListAsStrings, architectureListAsVars), @@ -622,7 +837,7 @@ public: std::tuple (&LocationProperties::arm64, Architecture::arm64), std::tuple (&LocationProperties::arm64ec, Architecture::arm64ec) }) { - const auto archAndFormat = toString (arch) + " " + format; + const auto archAndFormat = getArchitectureValueString (arch) + " " + format; props.add (new TextPropertyComponentWithEnablement (locationProps.*member, pluginBinaryCopyStepValue, archAndFormat + " Binary Location", 1024, false), "The folder in which the compiled " + archAndFormat + " binary should be placed."); } @@ -683,7 +898,7 @@ public: auto* e = configsGroup->createNewChildElement ("ProjectConfiguration"); e->setAttribute ("Include", config.createMSVCConfigName (arch)); e->createNewChildElement ("Configuration")->addTextElement (config.getName()); - e->createNewChildElement ("Platform")->addTextElement (toString (arch)); + e->createNewChildElement ("Platform")->addTextElement (getArchitectureValueString (arch)); } } } @@ -1492,6 +1707,8 @@ public: String getExtraPostBuildSteps (const MSVCBuildConfiguration& config, Architecture arch) const { + using Builder = MSVCScriptBuilder; + const auto copyBuildOutputIntoBundle = [&] (const StringArray& segments) { return "copy /Y " @@ -1598,34 +1815,100 @@ public: if (writerTarget == nullptr) return ""; - const auto writer = writerTarget->getExpandedConfigTargetPath (config) - + "\\" - + writerTarget->getBinaryNameWithSuffix (config); + const auto helperExecutablePath = writerTarget->getExpandedConfigTargetPath (config) + + "\\" + + writerTarget->getBinaryNameWithSuffix (config); - // moduleinfotool doesn't handle Windows-style path separators properly when computing the bundle name - const auto normalisedBundlePath = getOwner().getOutDirFile (config, segments[0]).replace ("\\", "/"); - const auto contentsDir = normalisedBundlePath + "\\Contents"; - const auto resourceDir = contentsDir + "\\Resources"; + { + // moduleinfotool doesn't handle Windows-style path separators properly when computing the bundle name + const auto normalisedBundlePath = getOwner().getOutDirFile (config, segments[0]).replace ("\\", "/"); + const auto contentsDir = normalisedBundlePath + "\\Contents"; + const auto resourceDir = contentsDir + "\\Resources"; + const auto manifestPath = (resourceDir + "\\moduleinfo.json"); + const auto resourceDirPath = resourceDir + "\\"; + const auto pluginName = getOwner().project.getPluginNameString(); - return "\r\ndel /s /q " + (contentsDir + "\\moduleinfo.json").quoted() + "\r\n" - "if not exist \"" + resourceDir + "\\\" del /s /q " + resourceDir.quoted() + " && mkdir " + resourceDir.quoted() + "\r\n" - + writer.quoted() - + " -create -version " - + getOwner().project.getVersionString().quoted() - + " -path " - + normalisedBundlePath.quoted() - + " -output " - + (resourceDir + "\\moduleinfo.json").quoted(); + const auto manifestInvocationString = StringArray + { + helperExecutablePath.quoted(), + "-create", + "-version", getOwner().project.getVersionString().quoted(), + "-path", normalisedBundlePath.quoted(), + "-output", manifestPath.quoted() + }.joinIntoString (" "); + + const auto crossCompilationPairs = + { + // This catches ARM64 and EC for x64 manifest generation + std::pair { Architecture::arm64, Architecture::win64 }, + std::pair { arch, arch } + }; + + Builder builder; + + builder.set ("manifest_generated", 0); + + for (auto [hostArch, targetArch] : crossCompilationPairs) + { + const StringArray expr + { + "\"$(PROCESSOR_ARCHITECTURE)\" == " + getVisualStudioArchitectureId (hostArch).quoted(), + "\"$(Platform)\"" " == " + getVisualStudioPlatformId (targetArch).quoted() + }; + + builder.ifAllConditionsTrue (expr, Builder{}.call ("_generate_manifest") + .set ("manifest_generated", 1)); + } + + const auto archMismatchErrorString = StringArray + { + "VST3 manifest generation is disabled for", + pluginName, + "because a", + getVisualStudioArchitectureId (arch), + "manifest helper cannot run on a host system", + "processor detected to be $(PROCESSOR_ARCHITECTURE)." + }.joinIntoString (" "); + + const auto architectureMatched = Builder{} + .ifelse ("exist " + manifestPath.quoted(), + Builder{}.deleteFile (manifestPath.quoted())) + .ifelse ("not exist " + resourceDirPath.quoted(), + Builder{}.mkdir (resourceDirPath.quoted())) + .runAndCheck (manifestInvocationString, + Builder{}.info ("Successfully generated a manifest for " + pluginName) + .jump ("_continue"), + Builder{}.info ("The manifest helper failed") + .jump ("_continue")); + + builder.ifelse ("%manifest_generated% equ 0", + Builder{}.jump ("_arch_mismatch")); + + builder.jump ("_continue"); + builder.labelledSection ("_generate_manifest", architectureMatched); + builder.labelledSection ("_arch_mismatch", Builder{}.info (archMismatchErrorString)); + builder.labelledSection ("_continue", ""); + + return builder.build(); + } }(); const auto pkgScript = copyBuildOutputIntoBundle (segments); const auto copyScript = copyBundleToInstallDirectory (segments, config.getBinaryPath (Ids::vst3BinaryLocation, arch)); - return pkgScript + manifestScript + copyScript; - } + const auto binCopyScript = config.isPluginBinaryCopyStepEnabled() + ? MSVCScriptBuilder{}.append ("copy /Y \"$(OutDir)$(TargetFileName)\" \"" + + config.getBinaryPath (Ids::vstBinaryLocation, arch) + + "\\$(TargetFileName)\"") + : MSVCScriptBuilder{}; - if (type == VSTPlugIn && config.isPluginBinaryCopyStepEnabled()) - return "copy /Y \"$(OutDir)$(TargetFileName)\" \"" + config.getBinaryPath (Ids::vstBinaryLocation, arch) + "\\$(TargetFileName)\""; + return MSVCScriptBuilder{} + .append (pkgScript) + .append (manifestScript) + .append (copyScript) + .append (binCopyScript) + .build(); + } return {}; } @@ -1635,15 +1918,18 @@ public: const auto createBundleStructure = [&] (const StringArray& segments) { auto directory = getOwner().getOutDirFile (config, ""); - String script; + MSVCScriptBuilder script; std::for_each (segments.begin(), std::prev (segments.end()), [&] (const auto& s) { directory += (directory.isEmpty() ? "" : "\\") + s; - script += "if not exist \"" + directory + "\\\" del /s /q " + directory.quoted() + " && mkdir " + directory.quoted() + "\r\n"; + + script.ifelse ("not exist " + (directory + "\\").quoted(), + MSVCScriptBuilder{}.deleteFile (directory.quoted()) + .mkdir (directory.quoted()).build()); }); - return script; + return script.build(); }; if (type == AAXPlugIn) @@ -1657,18 +1943,38 @@ public: String getPostBuildSteps (const MSVCBuildConfiguration& config, Architecture arch) const { - auto postBuild = config.getPostbuildCommandString().replace ("\n", "\r\n");; - auto extraPostBuild = getExtraPostBuildSteps (config, arch); + const auto post = config.getPostbuildCommandString(); + const auto extra = getExtraPostBuildSteps (config, arch); - return postBuild + String (postBuild.isNotEmpty() && extraPostBuild.isNotEmpty() ? "\r\n" : "") + extraPostBuild; + if (post.isNotEmpty() || extra.isNotEmpty()) + { + return MSVCScriptBuilder{} + .append ("cmd /c (") + .append (post.replace ("\n", "\r\n")) + .append (extra) + .append (")") + .build(); + } + + return ""; } String getPreBuildSteps (const MSVCBuildConfiguration& config, Architecture arch) const { - auto preBuild = config.getPrebuildCommandString().replace ("\n", "\r\n");; - auto extraPreBuild = getExtraPreBuildSteps (config, arch); + const auto pre = config.getPrebuildCommandString(); + const auto extra = getExtraPreBuildSteps (config, arch); - return preBuild + String (preBuild.isNotEmpty() && extraPreBuild.isNotEmpty() ? "\r\n" : "") + extraPreBuild; + if (pre.isNotEmpty() || extra.isNotEmpty()) + { + return MSVCScriptBuilder{} + .append ("cmd /c (") + .append (pre.replace ("\n", "\r\n")) + .append (extra) + .append (")") + .build(); + } + + return ""; } String getBinaryNameWithSuffix (const MSVCBuildConfiguration& config) const @@ -1751,7 +2057,7 @@ public: StringArray getAaxBundleStructure (const MSVCBuildConfiguration& config, Architecture arch) const { const auto dllName = config.getOutputFilename (".aaxplugin", false, type); - return { dllName, "Contents", toString (arch), dllName }; + return { dllName, "Contents", getArchitectureValueString (arch), dllName }; } StringArray getVst3BundleStructure (const MSVCBuildConfiguration& config, Architecture arch) const From 3ec470721764f187c8b1ac28de121904e930c311 Mon Sep 17 00:00:00 2001 From: Oliver James Date: Mon, 28 Oct 2024 01:20:27 +0000 Subject: [PATCH 023/557] Projucer: Don't run scripts on non-built targets --- .../Source/ProjectSaving/jucer_ProjectExport_MSVC.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index 9dbc75521a..ad943f07f2 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -1027,6 +1027,15 @@ public: libPath->addTextElement ("$(LibraryPath);" + librarySearchPaths.joinIntoString (";")); } } + + const auto enabled = config.getArchitectures().contains (arch) ? "true" : "false"; + + for (const auto optionName : { "PreBuildEventUseInBuild", "PostBuildEventUseInBuild" }) + { + auto* tag = props->createNewChildElement (optionName); + setConditionAttribute (*tag, config, arch); + tag->addTextElement (enabled); + } } } } From 0aaba52527649477fe366e06c8b1808e1ec9d06b Mon Sep 17 00:00:00 2001 From: Oliver James Date: Mon, 25 Nov 2024 16:16:22 +0000 Subject: [PATCH 024/557] Resave all projects --- .../DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj | 4 ++++ .../DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj | 4 ++++ .../Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj | 4 ++++ .../Builds/VisualStudio2019/AudioPluginHost_App.vcxproj | 4 ++++ .../Builds/VisualStudio2022/AudioPluginHost_App.vcxproj | 4 ++++ .../Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj | 4 ++++ .../Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj | 4 ++++ extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj | 4 ++++ extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj | 4 ++++ .../Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj | 4 ++++ .../Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj | 4 ++++ .../Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj | 4 ++++ 12 files changed, 48 insertions(+) diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj index b3c028a008..377388730c 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\App\ DemoRunner true + true + true $(SolutionDir)$(Platform)\$(Configuration)\App\ $(Platform)\$(Configuration)\App\ DemoRunner true + true + true diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj index 1145aa1b20..0aa583c9d3 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\App\ DemoRunner true + true + true $(SolutionDir)$(Platform)\$(Configuration)\App\ $(Platform)\$(Configuration)\App\ DemoRunner true + true + true diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj index 1b6285e6c5..435dc4331a 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\App\ AudioPerformanceTest true + true + true $(SolutionDir)$(Platform)\$(Configuration)\App\ $(Platform)\$(Configuration)\App\ AudioPerformanceTest true + true + true diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj index 6c59173e02..f44f8ff9db 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\App\ AudioPluginHost true + true + true $(SolutionDir)$(Platform)\$(Configuration)\App\ $(Platform)\$(Configuration)\App\ AudioPluginHost true + true + true diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj index d739b7def3..e68c5fab18 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\App\ AudioPluginHost true + true + true $(SolutionDir)$(Platform)\$(Configuration)\App\ $(Platform)\$(Configuration)\App\ AudioPluginHost true + true + true diff --git a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj index 93da43c13a..366bc40fbe 100644 --- a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj +++ b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\ConsoleApp\ BinaryBuilder true + true + true $(SolutionDir)$(Platform)\$(Configuration)\ConsoleApp\ $(Platform)\$(Configuration)\ConsoleApp\ BinaryBuilder true + true + true diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj index 8e795be0f1..761d9fbd53 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\App\ JUCE Network Graphics Demo true + true + true $(SolutionDir)$(Platform)\$(Configuration)\App\ $(Platform)\$(Configuration)\App\ JUCE Network Graphics Demo true + true + true diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj index fa6b37df64..f044782ebf 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\App\ Projucer true + true + true $(SolutionDir)$(Platform)\$(Configuration)\App\ $(Platform)\$(Configuration)\App\ Projucer true + true + true diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj index 874aefdc5b..04670029a0 100644 --- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\App\ Projucer true + true + true $(SolutionDir)$(Platform)\$(Configuration)\App\ $(Platform)\$(Configuration)\App\ Projucer true + true + true diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj index b603fc4129..d7db6fd365 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\ConsoleApp\ UnitTestRunner true + true + true $(SolutionDir)$(Platform)\$(Configuration)\ConsoleApp\ $(Platform)\$(Configuration)\ConsoleApp\ UnitTestRunner true + true + true diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj index 013f1f84f9..84f1c9922d 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\ConsoleApp\ UnitTestRunner true + true + true $(SolutionDir)$(Platform)\$(Configuration)\ConsoleApp\ $(Platform)\$(Configuration)\ConsoleApp\ UnitTestRunner true + true + true diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj index dc4478d920..0ce5ac00ff 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj @@ -47,10 +47,14 @@ $(Platform)\$(Configuration)\Dynamic Library\ juce_dll true + true + true $(SolutionDir)$(Platform)\$(Configuration)\Dynamic Library\ $(Platform)\$(Configuration)\Dynamic Library\ juce_dll true + true + true From 2b958c0416be4ae738fb43bfbd751595acf20636 Mon Sep 17 00:00:00 2001 From: Oliver James Date: Mon, 25 Nov 2024 16:18:07 +0000 Subject: [PATCH 025/557] Projucer: Add checks for incompatible LV2 architecture configurations --- .../ProjectSaving/jucer_ProjectExport_MSVC.h | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index ad943f07f2..805e17977e 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -1791,9 +1791,11 @@ public: return nullptr; }(); - const auto writer = writerTarget->getExpandedConfigTargetPath (config) + Builder builder; + const auto writer = (writerTarget->getExpandedConfigTargetPath (config) + "\\" - + writerTarget->getBinaryNameWithSuffix (config); + + writerTarget->getBinaryNameWithSuffix (config)).quoted() + + " \"$(OutDir)$(TargetFileName)\"\r\n"; const auto copyStep = "xcopy /E /H /I /K /R /Y \"$(OutDir)\" \"" + config.getBinaryPath (Ids::lv2BinaryLocation, arch) @@ -1801,9 +1803,12 @@ public: + config.getTargetBinaryNameString() + ".lv2\"\r\n"; - return writer.quoted() - + " \"$(OutDir)$(TargetFileName)\"\r\n" - + (config.isPluginBinaryCopyStepEnabled() ? copyStep : ""); + builder.runAndCheck (writer, + config.isPluginBinaryCopyStepEnabled() ? copyStep : Builder{}.info ("Sucessfully generated LV2 manifest").build(), + Builder{}.error ("Failed to generate LV2 manifest.") + .exit (-1)); + + return builder.build(); } if (type == VST3PlugIn) @@ -1941,6 +1946,40 @@ public: return script.build(); }; + if (type == LV2PlugIn) + { + const auto crossCompilationPairs = + { + // This catches ARM64 and EC for x64 manifest generation + std::pair { Architecture::arm64, Architecture::win64 }, + std::pair { arch, arch } + }; + + MSVCScriptBuilder builder; + + for (auto [hostArch, targetArch] : crossCompilationPairs) + { + const StringArray expr + { + "\"$(PROCESSOR_ARCHITECTURE)\" == " + getVisualStudioArchitectureId (hostArch).quoted(), + "\"$(Platform)\"" " == " + getVisualStudioPlatformId (targetArch).quoted() + }; + + builder.ifAllConditionsTrue (expr, MSVCScriptBuilder{}.jump ("_continue")); + } + + builder.error (StringArray + { + "\"$(Platform)\"", + "LV2 cross-compilation is not available on", + "\"$(PROCESSOR_ARCHITECTURE)\" hosts." + }.joinIntoString (" ")); + builder.exit (-1); + builder.labelledSection ("_continue", ""); + + return builder.build(); + } + if (type == AAXPlugIn) return createBundleStructure (getAaxBundleStructure (config, arch)); From 5023fc69d5680f4d8c1cd9c9140956fa99dd8d9c Mon Sep 17 00:00:00 2001 From: Oliver James Date: Mon, 18 Nov 2024 14:20:40 +0000 Subject: [PATCH 026/557] CMake: Passthrough OSX_DEPLOYMENT_TARGET when configuring juceaide --- extras/Build/juceaide/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extras/Build/juceaide/CMakeLists.txt b/extras/Build/juceaide/CMakeLists.txt index 7ef20eddf1..644744be48 100644 --- a/extras/Build/juceaide/CMakeLists.txt +++ b/extras/Build/juceaide/CMakeLists.txt @@ -111,6 +111,12 @@ else() set(ENV{CMAKE_GENERATOR_PLATFORM} "${CMAKE_HOST_SYSTEM_PROCESSOR}") endif() + set(PASSTHROUGH_ARGS "") + + if(CMAKE_OSX_DEPLOYMENT_TARGET) + list(APPEND PASSTHROUGH_ARGS "-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}") + endif() + # Looks like we're bootstrapping, reinvoke CMake execute_process(COMMAND "${CMAKE_COMMAND}" "." @@ -120,6 +126,7 @@ else() "-DCMAKE_BUILD_TYPE=Debug" "-DJUCE_BUILD_HELPER_TOOLS=ON" "-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}" + "${PASSTHROUGH_ARGS}" WORKING_DIRECTORY "${JUCE_SOURCE_DIR}" OUTPUT_VARIABLE command_output ERROR_VARIABLE command_output From e04cc9abe257b87b5b89b73a7359371ec25f6904 Mon Sep 17 00:00:00 2001 From: Oliver James Date: Mon, 18 Nov 2024 14:20:53 +0000 Subject: [PATCH 027/557] CMake: Passthrough CMAKE_XXX_COMPILER_LAUNCHER when configuring juceaide --- extras/Build/juceaide/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/extras/Build/juceaide/CMakeLists.txt b/extras/Build/juceaide/CMakeLists.txt index 644744be48..630681ca15 100644 --- a/extras/Build/juceaide/CMakeLists.txt +++ b/extras/Build/juceaide/CMakeLists.txt @@ -117,6 +117,14 @@ else() list(APPEND PASSTHROUGH_ARGS "-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}") endif() + if(CMAKE_C_COMPILER_LAUNCHER) + list(APPEND PASSTHROUGH_ARGS "-DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER}") + endif() + + if(CMAKE_CXX_COMPILER_LAUNCHER) + list(APPEND PASSTHROUGH_ARGS "-DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER}") + endif() + # Looks like we're bootstrapping, reinvoke CMake execute_process(COMMAND "${CMAKE_COMMAND}" "." From bd322d0f784c46e61fea59289d964bd59c632cc3 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Mon, 23 Sep 2024 12:31:51 +0100 Subject: [PATCH 028/557] String: Refactor a test function to be more generic --- modules/juce_core/text/juce_String.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/modules/juce_core/text/juce_String.cpp b/modules/juce_core/text/juce_String.cpp index 47cd5a64a6..ff18f600e4 100644 --- a/modules/juce_core/text/juce_String.cpp +++ b/modules/juce_core/text/juce_String.cpp @@ -2371,7 +2371,8 @@ public: { static void test (UnitTest& test, Random& r) { - String s (createRandomWideCharString (r)); + constexpr auto stringLength = 50; + const String s (createRandomWideCharString (r, stringLength)); using CharType = typename CharPointerType::CharType; CharType buffer[300]; @@ -2395,25 +2396,29 @@ public: } }; - static String createRandomWideCharString (Random& r) + static String createRandomWideCharString (Random& r, size_t length) { - juce_wchar buffer[50] = { 0 }; + std::vector characters (length, 0); - for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + for (auto& character : characters) { if (r.nextBool()) { do { - buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); + character = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); } - while (! CharPointer_UTF16::canRepresent (buffer[i])); + while (! CharPointer_UTF16::canRepresent (character)); } else - buffer[i] = (juce_wchar) (1 + r.nextInt (0xff)); + { + character = (juce_wchar) (1 + r.nextInt (0xff)); + } } - return CharPointer_UTF32 (buffer); + characters.push_back (0); + + return CharPointer_UTF32 (characters.data()); } void runTest() override From 0823ee6aed9c18f178aa85236d3c254b76b5eed5 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Mon, 25 Nov 2024 13:37:51 +0000 Subject: [PATCH 029/557] String: Fix the string length being passed in a UTF conversion test --- modules/juce_core/text/juce_String.cpp | 27 +++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/modules/juce_core/text/juce_String.cpp b/modules/juce_core/text/juce_String.cpp index ff18f600e4..ed06d7c93a 100644 --- a/modules/juce_core/text/juce_String.cpp +++ b/modules/juce_core/text/juce_String.cpp @@ -2375,24 +2375,25 @@ public: const String s (createRandomWideCharString (r, stringLength)); using CharType = typename CharPointerType::CharType; - CharType buffer[300]; + constexpr auto bytesPerCodeUnit = sizeof (CharType); + constexpr auto maxCodeUnitsPerCodePoint = 4 / bytesPerCodeUnit; - memset (buffer, 0xff, sizeof (buffer)); - CharPointerType (buffer).writeAll (s.toUTF32()); - test.expectEquals (String (CharPointerType (buffer)), s); + std::array codeUnits{}; + const auto codeUnitsSizeInBytes = codeUnits.size() * bytesPerCodeUnit; - memset (buffer, 0xff, sizeof (buffer)); - CharPointerType (buffer).writeAll (s.toUTF16()); - test.expectEquals (String (CharPointerType (buffer)), s); + std::memset (codeUnits.data(), 0xff, codeUnitsSizeInBytes); + CharPointerType (codeUnits.data()).writeAll (s.toUTF32()); + test.expectEquals (String (CharPointerType (codeUnits.data())), s); - memset (buffer, 0xff, sizeof (buffer)); - CharPointerType (buffer).writeAll (s.toUTF8()); - test.expectEquals (String (CharPointerType (buffer)), s); + std::memset (codeUnits.data(), 0xff, codeUnitsSizeInBytes); + CharPointerType (codeUnits.data()).writeAll (s.toUTF16()); + test.expectEquals (String (CharPointerType (codeUnits.data())), s); - const auto nullTerminator = std::find (buffer, buffer + std::size (buffer), (CharType) 0); - const auto numValidBytes = (int) std::distance (buffer, nullTerminator) * (int) sizeof (CharType); + std::memset (codeUnits.data(), 0xff, codeUnitsSizeInBytes); + CharPointerType (codeUnits.data()).writeAll (s.toUTF8()); + test.expectEquals (String (CharPointerType (codeUnits.data())), s); - test.expect (CharPointerType::isValidString (buffer, numValidBytes)); + test.expect (CharPointerType::isValidString (codeUnits.data(), codeUnitsSizeInBytes)); } }; From 6b08ced201245209c411046c07c24fd451a4541e Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 13 Sep 2024 14:22:35 +0100 Subject: [PATCH 030/557] VST3: Add support for parameter migration --- BREAKING_CHANGES.md | 21 ++ .../Builds/Android/app/CMakeLists.txt | 2 + .../VisualStudio2019/DemoRunner_App.vcxproj | 3 + .../DemoRunner_App.vcxproj.filters | 3 + .../VisualStudio2022/DemoRunner_App.vcxproj | 3 + .../DemoRunner_App.vcxproj.filters | 3 + .../Builds/Android/app/CMakeLists.txt | 2 + .../AudioPerformanceTest_App.vcxproj | 3 + .../AudioPerformanceTest_App.vcxproj.filters | 3 + .../Builds/Android/app/CMakeLists.txt | 2 + .../AudioPluginHost_App.vcxproj | 3 + .../AudioPluginHost_App.vcxproj.filters | 3 + .../AudioPluginHost_App.vcxproj | 3 + .../AudioPluginHost_App.vcxproj.filters | 3 + .../Builds/Android/app/CMakeLists.txt | 2 + .../NetworkGraphicsDemo_App.vcxproj | 3 + .../NetworkGraphicsDemo_App.vcxproj.filters | 3 + .../UnitTestRunner_ConsoleApp.vcxproj | 3 + .../UnitTestRunner_ConsoleApp.vcxproj.filters | 3 + .../UnitTestRunner_ConsoleApp.vcxproj | 3 + .../UnitTestRunner_ConsoleApp.vcxproj.filters | 3 + .../WindowsDLL_DynamicLibrary.vcxproj | 3 + .../WindowsDLL_DynamicLibrary.vcxproj.filters | 3 + .../detail/juce_PluginUtilities.h | 89 +------- .../juce_audio_plugin_client_VST3.cpp | 215 +++++++++++++----- .../juce_audio_processors.cpp | 1 + .../processors/juce_AudioProcessor.h | 6 +- .../utilities/juce_VST3ClientExtensions.cpp | 167 ++++++++++++++ .../utilities/juce_VST3ClientExtensions.h | 148 +++++++++++- 29 files changed, 565 insertions(+), 144 deletions(-) create mode 100644 modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 0fddbf7694..88f5a1bda3 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -37,6 +37,27 @@ The Javascript implementation increases compilation times while being required by only a select number of projects. +## Change + +The return type for VST3ClientExtensions::getCompatibleClasses() has changed +from a String to an array of 16 bytes. + +**Possible Issues** + +Any inherited classes overriding this method might fail to compile. + +**Workaround** + +Either explicitly switch to creating a 16-byte std::array or use +VST3ClientExtensions::toInterfaceId() to convert a string to a 16-byte array. + +**Rationale** + +As part of adding functionality to support migrating parameter IDs from +compatible plugins it was useful to switch to a safer type for representing +VST3 interface IDs that closer matches the VST3 SDK types. + + ## Change The VBlankAttachment class' inheritance from the ComponentPeer::VBlankListener diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt index 62f2831a24..23f6137865 100644 --- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt +++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt @@ -860,6 +860,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h" + "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h" "../../../../../modules/juce_audio_processors/juce_audio_processors.cpp" "../../../../../modules/juce_audio_processors/juce_audio_processors.mm" @@ -3455,6 +3456,7 @@ set_source_files_properties( "../../../../../modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h" + "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h" "../../../../../modules/juce_audio_processors/juce_audio_processors.cpp" "../../../../../modules/juce_audio_processors/juce_audio_processors.mm" diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj index 377388730c..9247d2cf1c 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj @@ -1095,6 +1095,9 @@ true + + true + true diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters index 6f25f4f2bb..25877052c3 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters @@ -1804,6 +1804,9 @@ JUCE Modules\juce_audio_processors\utilities + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj index 0aa583c9d3..d97ebc4639 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj @@ -1095,6 +1095,9 @@ true + + true + true diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters index 730cd91e51..c1ba09e988 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters @@ -1804,6 +1804,9 @@ JUCE Modules\juce_audio_processors\utilities + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt index 34171f6197..f9d6992c03 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt @@ -815,6 +815,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h" + "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h" "../../../../../modules/juce_audio_processors/juce_audio_processors.cpp" "../../../../../modules/juce_audio_processors/juce_audio_processors.mm" @@ -3070,6 +3071,7 @@ set_source_files_properties( "../../../../../modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h" + "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h" "../../../../../modules/juce_audio_processors/juce_audio_processors.cpp" "../../../../../modules/juce_audio_processors/juce_audio_processors.mm" diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj index 435dc4331a..e64a688c9c 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj @@ -1055,6 +1055,9 @@ true + + true + true diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters index 7a475a3df1..b05a019507 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters @@ -1594,6 +1594,9 @@ JUCE Modules\juce_audio_processors\utilities + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt index 35e7501e7e..a88bfc6b68 100644 --- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt @@ -848,6 +848,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h" + "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h" "../../../../../modules/juce_audio_processors/juce_audio_processors.cpp" "../../../../../modules/juce_audio_processors/juce_audio_processors.mm" @@ -3256,6 +3257,7 @@ set_source_files_properties( "../../../../../modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h" + "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h" "../../../../../modules/juce_audio_processors/juce_audio_processors.cpp" "../../../../../modules/juce_audio_processors/juce_audio_processors.mm" diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj index f44f8ff9db..c37d716ce5 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj @@ -1063,6 +1063,9 @@ true + + true + true diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters index c5886faeb6..957d187b47 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters @@ -1669,6 +1669,9 @@ JUCE Modules\juce_audio_processors\utilities + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj index e68c5fab18..b28adb2059 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj @@ -1063,6 +1063,9 @@ true + + true + true diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters index aad4602684..5e4d2e64e9 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters @@ -1669,6 +1669,9 @@ JUCE Modules\juce_audio_processors\utilities + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt index e46ad7a010..be29f69e17 100644 --- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt +++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt @@ -819,6 +819,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h" + "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h" "../../../../../modules/juce_audio_processors/juce_audio_processors.cpp" "../../../../../modules/juce_audio_processors/juce_audio_processors.mm" @@ -3154,6 +3155,7 @@ set_source_files_properties( "../../../../../modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h" + "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp" "../../../../../modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h" "../../../../../modules/juce_audio_processors/juce_audio_processors.cpp" "../../../../../modules/juce_audio_processors/juce_audio_processors.mm" diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj index 761d9fbd53..56d8b7707e 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj @@ -1055,6 +1055,9 @@ true + + true + true diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters index f3ddc3d536..b954c7a5de 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters @@ -1624,6 +1624,9 @@ JUCE Modules\juce_audio_processors\utilities + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj index d7db6fd365..6451d6a7fc 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj @@ -1071,6 +1071,9 @@ true + + true + true diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters index 399197e763..3082400365 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -1717,6 +1717,9 @@ JUCE Modules\juce_audio_processors\utilities + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj index 84f1c9922d..7a0e63f8eb 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj @@ -1071,6 +1071,9 @@ true + + true + true diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters index 97053ec167..182b38de9c 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -1717,6 +1717,9 @@ JUCE Modules\juce_audio_processors\utilities + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj index 0ce5ac00ff..28d7d579de 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj @@ -1054,6 +1054,9 @@ true + + true + true diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters index b6dcc7091b..343fe5911f 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters @@ -1621,6 +1621,9 @@ JUCE Modules\juce_audio_processors\utilities + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors diff --git a/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h b/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h index 1e7744f94c..ca2193dd96 100644 --- a/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h +++ b/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h @@ -66,88 +66,6 @@ struct PluginUtilities return hostType; } - // NB: Nasty old-fashioned code in here because it's copied from the Steinberg example code. - static void getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16]) - { - #if JUCE_WINDOWS - const auto juce_sprintf = [] (auto&& head, auto&&... tail) { sprintf_s (head, (size_t) numElementsInArray (head), tail...); }; - const auto juce_strcpy = [] (auto&& head, auto&&... tail) { strcpy_s (head, (size_t) numElementsInArray (head), tail...); }; - const auto juce_strcat = [] (auto&& head, auto&&... tail) { strcat_s (head, (size_t) numElementsInArray (head), tail...); }; - const auto juce_sscanf = [] (auto&&... args) { sscanf_s (args...); }; - #else - const auto juce_sprintf = [] (auto&& head, auto&&... tail) { snprintf (head, (size_t) numElementsInArray (head), tail...); }; - const auto juce_strcpy = [] (auto&&... args) { strcpy (args...); }; - const auto juce_strcat = [] (auto&&... args) { strcat (args...); }; - const auto juce_sscanf = [] (auto&&... args) { sscanf (args...); }; - #endif - - char uidString[33]; - - const int vstfxid = (('V' << 16) | ('S' << 8) | (forControllerUID ? 'E' : 'T')); - char vstfxidStr[7] = { 0 }; - juce_sprintf (vstfxidStr, "%06X", vstfxid); - - juce_strcpy (uidString, vstfxidStr); - - char uidStr[9] = { 0 }; - juce_sprintf (uidStr, "%08X", JucePlugin_VSTUniqueID); - juce_strcat (uidString, uidStr); - - char nameidStr[3] = { 0 }; - const size_t len = strlen (JucePlugin_Name); - - for (size_t i = 0; i <= 8; ++i) - { - juce::uint8 c = i < len ? static_cast (JucePlugin_Name[i]) : 0; - - if (c >= 'A' && c <= 'Z') - c += 'a' - 'A'; - - juce_sprintf (nameidStr, "%02X", c); - juce_strcat (uidString, nameidStr); - } - - unsigned long p0; - unsigned int p1, p2; - unsigned int p3[8]; - - juce_sscanf (uidString, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", - &p0, &p1, &p2, &p3[0], &p3[1], &p3[2], &p3[3], &p3[4], &p3[5], &p3[6], &p3[7]); - - union q0_u { - uint32 word; - uint8 bytes[4]; - } q0; - - union q1_u { - uint16 half; - uint8 bytes[2]; - } q1, q2; - - q0.word = static_cast (p0); - q1.half = static_cast (p1); - q2.half = static_cast (p2); - - // VST3 doesn't use COM compatible UUIDs on non windows platforms - #if ! JUCE_WINDOWS - q0.word = ByteOrder::swap (q0.word); - q1.half = ByteOrder::swap (q1.half); - q2.half = ByteOrder::swap (q2.half); - #endif - - for (int i = 0; i < 4; ++i) - uuid[i+0] = q0.bytes[i]; - - for (int i = 0; i < 2; ++i) - uuid[i+4] = q1.bytes[i]; - - for (int i = 0; i < 2; ++i) - uuid[i+6] = q2.bytes[i]; - - for (int i = 0; i < 8; ++i) - uuid[i+8] = static_cast (p3[i]); - } - #if JucePlugin_Build_VST static bool handleManufacturerSpecificVST2Opcode ([[maybe_unused]] int32 index, [[maybe_unused]] pointer_sized_int value, @@ -158,9 +76,10 @@ struct PluginUtilities if ((index == (int32) ByteOrder::bigEndianInt ("stCA") || index == (int32) ByteOrder::bigEndianInt ("stCa")) && value == (int32) ByteOrder::bigEndianInt ("FUID") && ptr != nullptr) { - uint8 fuid[16]; - getUUIDForVST2ID (false, fuid); - ::memcpy (ptr, fuid, 16); + const auto uidString = VST3ClientExtensions::convertVST2PluginId (JucePlugin_VSTUniqueID, JucePlugin_Name, VST3ClientExtensions::InterfaceType::component); + MemoryBlock uidValue; + uidValue.loadFromHexString (uidString); + uidValue.copyTo (ptr, 0, uidValue.getSize()); return true; } #endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp index aa6b9800be..63045e3dbd 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp @@ -38,6 +38,38 @@ //============================================================================== #if JucePlugin_Build_VST3 +#if JUCE_VST3_CAN_REPLACE_VST2 && ! JUCE_FORCE_USE_LEGACY_PARAM_IDS && ! JUCE_IGNORE_VST3_MISMATCHED_PARAMETER_ID_WARNING + + // If you encounter this error there may be an issue migrating parameter + // automation between sessions saved using the VST2 and VST3 versions of this + // plugin. + // + // If you have released neither a VST2 or VST3 version of the plugin, + // consider only releasing a VST3 version and disabling JUCE_VST3_CAN_REPLACE_VST2. + // + // If you have released a VST2 version of the plugin but have not yet released + // a VST3 version of the plugin, consider enabling JUCE_FORCE_USE_LEGACY_PARAM_IDS. + // This will ensure that the parameter IDs remain compatible between both the + // VST2 and VST3 versions of the plugin in all hosts. + // + // If you have released a VST3 version of the plugin but have not released a + // VST2 version of the plugin, enable JUCE_IGNORE_VST3_MISMATCHED_PARAMETER_ID_WARNING. + // DO NOT change the JUCE_VST3_CAN_REPLACE_VST2 or JUCE_FORCE_USE_LEGACY_PARAM_IDS + // values as this will break compatibility with currently released VST3 + // versions of the plugin. + // + // If you have already released a VST2 and VST3 version of the plugin you may + // find in some hosts when a session containing automation data is saved using + // the VST2 or VST3 version, and is later loaded using the other version, the + // automation data will fail to control any of the parameters in the plugin as + // the IDs for these parameters are different. To fix parameter automation for + // the VST3 plugin when a session was saved with the VST2 plugin, implement + // VST3ClientExtensions::getCompatibleParameterIds() and enable + // JUCE_IGNORE_VST3_MISMATCHED_PARAMETER_ID_WARNING. + + #error You may have a conflict with parameter automation between VST2 and VST3 versions of your plugin. See the comment above for more details. +#endif + JUCE_BEGIN_NO_SANITIZE ("vptr") #if JUCE_PLUGINHOST_VST3 @@ -97,23 +129,33 @@ JUCE_BEGIN_NO_SANITIZE ("vptr") namespace juce { -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4310) -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wall") - -#if JUCE_VST3_CAN_REPLACE_VST2 - static Steinberg::FUID getFUIDForVST2ID (bool forControllerUID) - { - Steinberg::TUID uuid; - detail::PluginUtilities::getUUIDForVST2ID (forControllerUID, (uint8*) uuid); - return Steinberg::FUID (uuid); - } -#endif - -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +using VST3InterfaceType = VST3ClientExtensions::InterfaceType; +using VST3InterfaceId = VST3ClientExtensions::InterfaceId; using namespace Steinberg; +static FUID toSteinbergUID (const VST3InterfaceId& uid) +{ + return FUID::fromTUID ((const char*) (uid.data())); +} + +static VST3InterfaceId toVST3InterfaceId (const TUID uid) +{ + VST3InterfaceId iid; + std::memcpy (iid.data(), uid, iid.size()); + return iid; +} + +static VST3InterfaceId getInterfaceId (VST3InterfaceType interfaceType) +{ + #if JUCE_VST3_CAN_REPLACE_VST2 + if (interfaceType == VST3InterfaceType::controller || interfaceType == VST3InterfaceType::component) + return VST3ClientExtensions::convertVST2PluginId (JucePlugin_VSTUniqueID, JucePlugin_Name, interfaceType); + #endif + + return VST3ClientExtensions::convertJucePluginId (JucePlugin_ManufacturerCode, JucePlugin_PluginCode, interfaceType); +} + //============================================================================== #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE double getScaleFactorForWindow (HWND); @@ -502,13 +544,15 @@ public: #if JUCE_FORCE_USE_LEGACY_PARAM_IDS return static_cast (paramIndex); #else + jassert (paramIndex < vstParamIDs.size()); return vstParamIDs.getReference (paramIndex); #endif } AudioProcessorParameter* getParamForVSTParamID (Vst::ParamID paramID) const noexcept { - return paramMap[static_cast (paramID)]; + const auto iter = paramMap.find (paramID); + return iter != paramMap.end() ? iter->second : nullptr; } AudioProcessorParameter* getBypassParameter() const noexcept @@ -561,8 +605,61 @@ public: bool isUsingManagedParameters() const noexcept { return juceParameters.isUsingManagedParameters(); } + std::map getParameterMap (const VST3InterfaceId& pluginId) const + { + const auto iter = compatibleParameterIdMap.find (pluginId); + return iter != compatibleParameterIdMap.end() ? iter->second + : std::map{}; + } + + AudioProcessorParameter* getParameter (const String& juceParamId) const + { + const auto iter = juceIdParameterMap.find (juceParamId); + return iter != juceIdParameterMap.end() ? iter->second : nullptr; + } + + void updateParameterMapping() + { + static const auto currentPluginId = getInterfaceId (VST3InterfaceType::component); + + compatibleParameterIdMap = {}; + compatibleParameterIdMap[currentPluginId] = paramMap; + + if (const auto* ext = audioProcessor->getVST3ClientExtensions()) + { + for (auto& compatibleClass : ext->getCompatibleClasses()) + { + auto& parameterIdMap = compatibleParameterIdMap[compatibleClass]; + + for (auto [oldParamId, newParamId] : ext->getCompatibleParameterIds (compatibleClass)) + { + auto* parameter = getParameter (newParamId); + parameterIdMap[oldParamId] = parameter; + + // This means a parameter ID returned by getCompatibleParameterIds() + // does not match any parameters declared in the plugin. All IDs must + // match an existing parameter, or return an empty string to indicate + // there is no parameter to map to. + jassert (parameter != nullptr || newParamId.isEmpty()); + + // This means getCompatibleParameterIds() returned a parameter mapping + // that will hide a parameter in the current plugin! If this is due to + // an ID collision between plugin versions, you may be able to determine + // the mapping to report based on setStateInformation(). If you've + // already done this you can safely ignore this warning. If there is no + // way to determine the difference between the two plugin versions in + // setStateInformation() the best course of action is to remove the + // problematic parameter from the mapping. + jassert (compatibleClass != currentPluginId + || getParamForVSTParamID (oldParamId) == nullptr + || parameter == getParamForVSTParamID (oldParamId)); + } + } + } + } + //============================================================================== - inline static const FUID iid { TUID INLINE_UID (0x0101ABAB, 0xABCDEF01, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) }; + inline static const FUID iid = toSteinbergUID (getInterfaceId (VST3InterfaceType::processor)); private: //============================================================================== @@ -570,7 +667,7 @@ private: { parameterGroups = audioProcessor->getParameterTree().getSubgroups (true); - #if JUCE_DEBUG + #if JUCE_ASSERTIONS_ENABLED_OR_LOGGED auto allGroups = parameterGroups; allGroups.add (&audioProcessor->getParameterTree()); std::unordered_set unitIDs; @@ -633,7 +730,8 @@ private: } vstParamIDs.add (vstParamID); - paramMap.set (static_cast (vstParamID), juceParam); + paramMap[vstParamID] = juceParam; + juceIdParameterMap[LegacyAudioParameter::getParamID (juceParam, false)] = juceParam; } auto numPrograms = audioProcessor->getNumPrograms(); @@ -650,7 +748,7 @@ private: programParamID = static_cast (i++); vstParamIDs.add (programParamID); - paramMap.set (static_cast (programParamID), ownedProgramParameter.get()); + paramMap[programParamID] = ownedProgramParameter.get(); } cachedParamValues = CachedParamValues { { vstParamIDs.begin(), vstParamIDs.end() } }; @@ -658,20 +756,13 @@ private: Vst::ParamID generateVSTParamIDForParam (const AudioProcessorParameter* param) { - auto juceParamID = LegacyAudioParameter::getParamID (param, false); + const auto juceParamID = LegacyAudioParameter::getParamID (param, false); - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS return static_cast (juceParamID.getIntValue()); - #else - auto paramHash = static_cast (juceParamID.hashCode()); - - #if JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS - // studio one doesn't like negative parameters - paramHash &= ~(((Vst::ParamID) 1) << (sizeof (Vst::ParamID) * 8 - 1)); + #else + return VST3ClientExtensions::convertJuceParameterId (juceParamID, JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS); #endif - - return paramHash; - #endif } //============================================================================== @@ -679,6 +770,8 @@ private: CachedParamValues cachedParamValues; Vst::ParamID bypassParamID = 0, programParamID = static_cast (paramPreset); bool bypassIsRegularParameter = false; + std::map> compatibleParameterIdMap; + std::map juceIdParameterMap; //============================================================================== std::atomic refCount { 0 }; @@ -686,7 +779,7 @@ private: //============================================================================== LegacyAudioParametersWrapper juceParameters; - HashMap paramMap; + std::map paramMap; std::unique_ptr ownedBypassParameter, ownedProgramParameter; Array parameterGroups; @@ -764,6 +857,7 @@ static void setValueAndNotifyIfChanged (AudioProcessorParameter& param, float ne class JuceVST3EditController final : public Vst::EditController, public Vst::IMidiMapping, public Vst::IUnitInfo, + public Vst::IRemapParamID, public Vst::ChannelContext::IInfoListener, #if JucePlugin_Enable_ARA public Presonus::IPlugInViewEmbedding, @@ -783,12 +877,7 @@ public: } //============================================================================== - - #if JUCE_VST3_CAN_REPLACE_VST2 - inline static const FUID iid = getFUIDForVST2ID (true); - #else - inline static const FUID iid { TUID INLINE_UID (0xABCDEF01, 0x1234ABCD, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) }; - #endif + inline static const FUID iid = toSteinbergUID (getInterfaceId (VST3InterfaceType::controller)); //============================================================================== JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Winconsistent-missing-override") @@ -1028,6 +1117,30 @@ public: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramChangeParameter) }; + //============================================================================== + tresult PLUGIN_API getCompatibleParamID (const TUID pluginToReplaceUID, + Vst::ParamID oldParamID, + Vst::ParamID& newParamID) override + { + const auto parameterMap = audioProcessor->getParameterMap (toVST3InterfaceId (pluginToReplaceUID)); + const auto iter = parameterMap.find (oldParamID); + + if (iter == parameterMap.end()) + { + // This suggests a host is trying to load a plugin and parameter ID + // combination that hasn't been accounted for in getCompatibleParameterIds(). + // Override this method in VST3ClientExtensions and return a suitable + // parameter mapping to silence this warning. + jassertfalse; + return kResultFalse; + } + + const auto* parameter = iter->second; + newParamID = parameter != nullptr ? audioProcessor->getVSTParamIDForIndex (parameter->getParameterIndex()) + : 0xffffffff; + return kResultTrue; + } + //============================================================================== tresult PLUGIN_API setChannelContextInfos (Vst::IAttributeList* list) override { @@ -1101,8 +1214,10 @@ public: } } + audioProcessor->updateParameterMapping(); + if (auto* handler = getComponentHandler()) - handler->restartComponent (Vst::kParamValuesChanged); + handler->restartComponent (Vst::kParamValuesChanged | Vst::kParamIDMappingChanged); return kResultOk; } @@ -1549,6 +1664,7 @@ private: UniqueBase{}, UniqueBase{}, UniqueBase{}, + UniqueBase{}, UniqueBase{}, SharedBase{}, UniqueBase{}, @@ -1557,6 +1673,9 @@ private: #endif SharedBase{}); + if (targetIID == Vst::IRemapParamID::iid) + jassertfalse; + if (result.isOk()) return result; @@ -2508,7 +2627,7 @@ private: return createARAFactory(); } - inline static const FUID iid { TUID INLINE_UID (0xABCDEF01, 0xA1B2C3D4, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) }; + inline static const FUID iid = toSteinbergUID (getInterfaceId (VST3InterfaceType::ara)); private: //============================================================================== @@ -2581,11 +2700,7 @@ public: AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } //============================================================================== - #if JUCE_VST3_CAN_REPLACE_VST2 - inline static const FUID iid = getFUIDForVST2ID (false); - #else - inline static const FUID iid { TUID INLINE_UID (0xABCDEF01, 0x9182FAEB, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) }; - #endif + inline static const FUID iid = toSteinbergUID (getInterfaceId (VST3InterfaceType::component)); JUCE_DECLARE_VST3_COM_REF_METHODS @@ -3998,15 +4113,7 @@ public: Array oldArray; for (const auto& uid : extensions->getCompatibleClasses()) - { - // All UIDs returned from getCompatibleClasses should be 32 characters long - jassert (uid.length() == 32); - - // All UIDs returned from getCompatibleClasses should be in hex notation - jassert (uid.containsOnly ("ABCDEF0123456789")); - - oldArray.add (uid); - } + oldArray.add (String::toHexString (uid.data(), (int) uid.size(), 0)); return oldArray; }()); @@ -4034,7 +4141,7 @@ public: return kNotImplemented; } - inline static const FUID iid { TUID INLINE_UID (0xABCDEF01, 0xC0DEF00D, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) }; + inline static const FUID iid = toSteinbergUID (getInterfaceId (VST3InterfaceType::compatibility)); private: std::atomic refCount { 1 }; diff --git a/modules/juce_audio_processors/juce_audio_processors.cpp b/modules/juce_audio_processors/juce_audio_processors.cpp index 873ca3ade0..a3d5d7ed9e 100644 --- a/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/modules/juce_audio_processors/juce_audio_processors.cpp @@ -231,6 +231,7 @@ private: #include "utilities/juce_PluginHostType.cpp" #include "utilities/juce_AAXClientExtensions.cpp" #include "utilities/juce_VST2ClientExtensions.cpp" +#include "utilities/juce_VST3ClientExtensions.cpp" #include "utilities/ARA/juce_ARA_utils.cpp" #include "format_types/juce_LV2PluginFormat.cpp" diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 74376b5096..103d303c12 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1154,7 +1154,11 @@ public: See also the helper function getXmlFromBinary() for loading settings as XML. - @see setCurrentProgramStateInformation + VST3ClientExtensions::getCompatibleParameterIds() will always be called after + setStateInformation() therefore you can use information from the plugin state + to determine which parameter mapping to use if necessary. + + @see setCurrentProgramStateInformation, VST3ClientExtensions::getCompatibleParameterIds */ virtual void setStateInformation (const void* data, int sizeInBytes) = 0; diff --git a/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp b/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp new file mode 100644 index 0000000000..b3cd133bb8 --- /dev/null +++ b/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp @@ -0,0 +1,167 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +std::map VST3ClientExtensions::getCompatibleParameterIds (const InterfaceId&) const +{ + return {}; +} + +VST3ClientExtensions::InterfaceId VST3ClientExtensions::convertJucePluginId (uint32_t manufacturerCode, + uint32_t pluginCode, + InterfaceType interfaceType) +{ + const auto word0 = std::invoke ([&]() -> uint32_t + { + switch (interfaceType) + { + case InterfaceType::ara: [[fallthrough]]; + case InterfaceType::controller: [[fallthrough]]; + case InterfaceType::compatibility: [[fallthrough]]; + case InterfaceType::component: return 0xABCDEF01; + case InterfaceType::processor: return 0x0101ABAB; + } + + jassertfalse; + return 0; + }); + + const auto word1 = std::invoke ([&]() -> uint32_t + { + switch (interfaceType) + { + case InterfaceType::ara: return 0xA1B2C3D4; + case InterfaceType::controller: return 0x1234ABCD; + case InterfaceType::compatibility: return 0xC0DEF00D; + case InterfaceType::component: return 0x9182FAEB; + case InterfaceType::processor: return 0xABCDEF01; + } + + jassertfalse; + return 0; + }); + + std::array data; + std::memcpy (&data[0], &word0, sizeof (word0)); + std::memcpy (&data[4], &word1, sizeof (word1)); + std::memcpy (&data[8], &manufacturerCode, sizeof (manufacturerCode)); + std::memcpy (&data[12], &pluginCode, sizeof (pluginCode)); + + return data; +} + +VST3ClientExtensions::InterfaceId VST3ClientExtensions::convertVST2PluginId (uint32_t pluginCode, + const String& pluginName, + InterfaceType interfaceType) +{ + VST3ClientExtensions::InterfaceId iid{}; + + iid[0] = (std::byte) 'V'; + iid[1] = (std::byte) 'S'; + iid[2] = (std::byte) std::invoke ([&] + { + switch (interfaceType) + { + case InterfaceType::controller: return 'E'; + case InterfaceType::component: return 'T'; + case InterfaceType::ara: [[fallthrough]]; + case InterfaceType::compatibility: [[fallthrough]]; + case InterfaceType::processor: break; + } + + // A VST2 plugin only has two interfaces + // - component (the audio effect) + // - controller (the editor/UI) + jassertfalse; + return '\0'; + }); + iid[3] = (std::byte) (pluginCode >> 24); + iid[4] = (std::byte) (pluginCode >> 16); + iid[5] = (std::byte) (pluginCode >> 8); + iid[6] = (std::byte) pluginCode; + + for (const auto [index, character] : enumerate (pluginName, (size_t) 7)) + { + if (index >= iid.size()) + break; + + iid[index] = (std::byte) CharacterFunctions::toLowerCase (character); + } + + #if JUCE_WINDOWS + std::swap (iid[0], iid[3]); + std::swap (iid[1], iid[2]); + std::swap (iid[4], iid[5]); + std::swap (iid[6], iid[7]); + #endif + + return iid; +} + +uint32_t VST3ClientExtensions::convertJuceParameterId (const String& parameterId, bool studioOneCompatible) +{ + auto hash = (uint32_t) (parameterId.hashCode()); + + if (studioOneCompatible) + hash &= 0x7fffffff; + + return hash; +} + +VST3ClientExtensions::InterfaceId VST3ClientExtensions::toInterfaceId (const String& interfaceIdString) +{ + jassert (interfaceIdString.length() == 32); + jassert (interfaceIdString.containsOnly ("0123456789abcdefABCDEF")); + + return { (std::byte) interfaceIdString.substring ( 0, 2).getHexValue32(), + (std::byte) interfaceIdString.substring ( 2, 4).getHexValue32(), + (std::byte) interfaceIdString.substring ( 4, 6).getHexValue32(), + (std::byte) interfaceIdString.substring ( 6, 8).getHexValue32(), + (std::byte) interfaceIdString.substring ( 8, 10).getHexValue32(), + (std::byte) interfaceIdString.substring (10, 12).getHexValue32(), + (std::byte) interfaceIdString.substring (12, 14).getHexValue32(), + (std::byte) interfaceIdString.substring (14, 16).getHexValue32(), + (std::byte) interfaceIdString.substring (16, 18).getHexValue32(), + (std::byte) interfaceIdString.substring (18, 20).getHexValue32(), + (std::byte) interfaceIdString.substring (20, 22).getHexValue32(), + (std::byte) interfaceIdString.substring (22, 24).getHexValue32(), + (std::byte) interfaceIdString.substring (24, 26).getHexValue32(), + (std::byte) interfaceIdString.substring (26, 28).getHexValue32(), + (std::byte) interfaceIdString.substring (28, 30).getHexValue32(), + (std::byte) interfaceIdString.substring (30, 32).getHexValue32() }; +} + +} // namespace juce diff --git a/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h b/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h index 09c8cdb73a..cee4e6a305 100644 --- a/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h +++ b/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h @@ -104,16 +104,156 @@ struct VST3ClientExtensions */ virtual bool getPluginHasMainInput() const { return true; } - /** This function should return the UIDs of any compatible VST2 plug-ins. + using InterfaceId = std::array; - Each item in the vector should be a 32-character string consisting only - of the characters 0-9 and A-F. + /** This function should return the UIDs of any compatible VST2 or VST3 + plug-ins. This information will be used to implement the IPluginCompatibility interface. Hosts can use this interface to determine whether this VST3 is capable of replacing a given VST2. + + Each compatible class is a 16-byte array that corresponds to the VST3 + interface ID for the class implementing the IComponent interface. + For VST2 or JUCE plugins these IDs can be determined in the following + ways: + - Use convertVST2PluginId() for VST2 plugins or JUCE VST3 plugins with + JUCE_VST3_CAN_REPLACE_VST3 enabled + - Use convertJucePluginId() for any other JUCE VST3 plugins + + If JUCE_VST3_CAN_REPLACE_VST2 is enabled the VST3 plugin will have the + same identifier as the VST2 plugin and therefore there will be no need + to implement this function. + + If the parameter IDs between compatible versions differ + getCompatibleParameterIds() should also be overridden. However, unlike + getCompatibleParameterIds() this function should remain constant and + always return the same IDs. + + @see getCompatibleParameterIds() */ - virtual std::vector getCompatibleClasses() const { return {}; } + virtual std::vector getCompatibleClasses() const { return {}; } + + /** This function should return a map of VST3 parameter IDs and the JUCE + parameters they map to. + + This information is used to implement the IRemapParameter interface. + Hosts can use this to preserve automation data when a session was saved + using a compatible plugin that has different parameter IDs. + + Not all hosts will take this information into account. Therefore, + parameter IDs should be maintained between plugin versions. For JUCE + plugins migrating from VST2 to VST3 the best method for achieving this + is enabling JUCE_FORCE_LEGACY_PARAM_IDS. However, if a plugin has + already been released without enabling this flag, this method offers an + alternative approach that won't cause any further compatibility issues. + + The key in the map is a VST3 parameter identifier or Vst::ParamID. For + VST2 or JUCE plugins these IDs can be determined in the following ways + - Use the parameter index for + - VST2 plugins + - JUCE VST3 plugins with JUCE_FORCE_LEGACY_PARAM_IDS enabled + - Any parameter that doesn't inherit from HostedAudioProcessorParameter + - Use convertJuceParameterId() for JUCE VST3 plugins where + JUCE_FORCE_LEGACY_PARAM_IDS is disabled + + The value in the map is the JUCE parameter ID for the parameter to map + to, or an empty string to indicate that there is no parameter to map to. + If a parameter doesn't inherit from HostedAudioProcessorParameter its ID + will be the parameter index as a string, for example "1". Otherwise + always use the actual parameter ID (even if JUCE_FORCE_LEGACY_PARAM_IDS + is enabled). + + In the unlikely event that two plugins share the same plugin ID, and + both have a different parameters that share the same parameter ID, it + may be possible to determine which version of the plugin is being loaded + during setStateInformation(). This method will always be called after + setStateInformation(), so that the map with the correct mapping can be + provided when queried. + + Below is an example of how you might implement this function for a JUCE + VST3 plugin where JUCE_VST3_CAN_REPLACE_VST2 is enabled, but + JUCE_FORCE_LEGACY_PARAM_IDS is disabled. + + @code + std::map getCompatibleParameterIds (const String&) const override + { + return { { 0, "Frequency" }, + { 1, "CutOff" }, + { 2, "Gain" }, + { 3, "Bypass" } }; + } + @endcode + + @param compatibleClass A plugin identifier, either for the current + plugin or one listed in getCompatibleClasses(). + This parameter allows the implementation to + return a different parameter map for each + compatible class. Use convertJucePluginId() and + convertVST2PluginId() to determine the class IDs + used by JUCE plugins. + + @returns A map where each key is a VST3 parameter ID in the compatible + plugin, and the value is the unique JUCE parameter ID in the + current plugin that it should be mapped to. + + @see getCompatibleClasses, convertJucePluginId, convertVST2PluginId, convertJuceParameterId + */ + virtual std::map getCompatibleParameterIds (const InterfaceId& compatibleClass) const; + + /** An enum indicating the various VST3 interface types. + + In most cases users shouldn't need to concern themselves with any + interfaces other than the component, which is used to report the actual + audio effect. + */ + enum class InterfaceType + { + ara, + controller, + compatibility, + component, + processor + }; + + /** Returns a 16-byte array indicating the VST3 interface ID used for a + given JUCE VST3 plugin. + + Internally this is what JUCE will use to assign an ID to each VST3 + interface, unless JUCE_VST3_CAN_REPLACE_VST2 is enabled. + + @see convertVST2PluginId, getCompatibleClasses, getCompatibleParameterIds + */ + static InterfaceId convertJucePluginId (uint32_t manufacturerCode, + uint32_t pluginCode, + InterfaceType interfaceType = InterfaceType::component); + + /** Returns a 16-byte array indicating the VST3 interface ID used for a + given VST2 plugin. + + Internally JUCE will use this method to assign an ID for the component + and controller interfaces when JUCE_VST3_CAN_REPLACE_VST2 is enabled. + + @convertJucePluginId, getCompatibleClasses, getCompatibleParameterIds + */ + static InterfaceId convertVST2PluginId (uint32_t pluginCode, + const String& pluginName, + InterfaceType interfaceType = InterfaceType::component); + + /** Returns the VST3 compatible parameter ID reported for a given JUCE + parameter. + + Internally JUCE will use this method to determine the Vst::ParamID for + a HostedAudioProcessorParameter, unless JUCE_FORCE_LEGACY_PARAM_IDS is + enabled, in which case it will use the parameter index. + + @see getCompatibleParameterIds + */ + static uint32_t convertJuceParameterId (const String& parameterId, + bool studioOneCompatible = true); + + /** Converts a 32-character hex notation string to a VST3 interface ID. */ + static InterfaceId toInterfaceId (const String& interfaceIdString); }; } // namespace juce From 5e803ded5fd6344d56dde772330a4a7785e9d14a Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 27 Sep 2024 16:26:59 +0100 Subject: [PATCH 031/557] Deprecations: Add ignore deprecation warning macros --- .../buffers/juce_AudioDataConverters.cpp | 6 ++---- modules/juce_audio_basics/midi/juce_MidiBuffer.cpp | 6 ++---- modules/juce_audio_basics/midi/juce_MidiMessage.cpp | 6 ++---- modules/juce_audio_devices/native/juce_Audio_ios.cpp | 4 ++-- .../codecs/juce_OggVorbisAudioFormat.cpp | 7 +++++-- .../format_types/juce_LV2SupportLibs.cpp | 5 +++-- .../format_types/juce_LegacyAudioParameter.cpp | 6 ++---- .../format_types/juce_VST3PluginFormat.cpp | 4 ++-- .../format_types/juce_VSTPluginFormat.cpp | 10 +++------- .../processors/juce_AudioProcessor.cpp | 6 ++---- modules/juce_core/containers/juce_Variant.cpp | 6 ++---- modules/juce_core/files/juce_DirectoryIterator.cpp | 6 ++---- modules/juce_core/files/juce_File.cpp | 6 ++---- .../juce_core/files/juce_RangedDirectoryIterator.cpp | 6 ++---- .../juce_core/files/juce_RangedDirectoryIterator.h | 6 ++---- modules/juce_core/memory/juce_ScopedPointer.h | 12 ++++-------- .../native/juce_AndroidDocument_android.cpp | 6 ++---- modules/juce_core/native/juce_Files_mac.mm | 4 ++-- modules/juce_core/native/juce_Files_windows.cpp | 4 ++-- modules/juce_core/native/juce_SharedCode_posix.h | 4 ++-- modules/juce_core/system/juce_CompilerWarnings.h | 8 ++++++++ modules/juce_core/text/juce_String.cpp | 10 ++++------ .../juce_data_structures/values/juce_ValueTree.cpp | 6 ++---- .../interprocess/juce_ConnectedChildProcess.cpp | 12 ++++-------- .../juce_events/native/juce_MessageManager_mac.mm | 4 ++-- modules/juce_graphics/fonts/juce_Font.cpp | 12 ++++-------- modules/juce_graphics/images/juce_Image.cpp | 6 ++---- modules/juce_graphics/juce_graphics_Harfbuzz.cpp | 8 +++++--- .../juce_gui_basics/native/juce_FileChooser_mac.mm | 4 ++-- .../native/juce_NSViewComponentPeer_mac.mm | 4 ++-- .../native/juce_PerScreenDisplayLinks_mac.h | 4 ++-- .../native/juce_UIViewComponentPeer_ios.mm | 4 ++-- modules/juce_gui_basics/native/juce_Windowing_mac.mm | 4 ++-- .../code_editor/juce_CodeEditorComponent.cpp | 6 ++---- .../juce_gui_extra/native/juce_AppleRemote_mac.mm | 4 ++-- .../native/juce_PushNotifications_mac.cpp | 4 ++-- .../native/juce_SystemTrayIcon_mac.cpp | 4 ++-- .../native/juce_WebBrowserComponent_linux.cpp | 4 ++-- .../native/juce_WebBrowserComponent_mac.mm | 12 ++++++------ modules/juce_opengl/native/juce_OpenGL_mac.h | 4 ++-- .../marketplace/juce_OnlineUnlockStatus.cpp | 6 ++---- modules/juce_video/native/juce_CameraDevice_mac.h | 12 ++++++------ 42 files changed, 114 insertions(+), 148 deletions(-) diff --git a/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp b/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp index 15450baa5b..192019ac77 100644 --- a/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp +++ b/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp @@ -35,8 +35,7 @@ namespace juce { -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample) { @@ -641,7 +640,6 @@ static AudioConversionTests audioConversionUnitTests; #endif -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS } // namespace juce diff --git a/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp b/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp index fa1dafc6bf..b089840e8b 100644 --- a/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp +++ b/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp @@ -221,8 +221,7 @@ MidiBufferIterator MidiBuffer::findNextSamplePosition (int samplePosition) const } //============================================================================== -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept : buffer (b), iterator (b.data.begin()) @@ -257,8 +256,7 @@ bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePositio return true; } -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS //============================================================================== //============================================================================== diff --git a/modules/juce_audio_basics/midi/juce_MidiMessage.cpp b/modules/juce_audio_basics/midi/juce_MidiMessage.cpp index 940cb00111..530a880f3a 100644 --- a/modules/juce_audio_basics/midi/juce_MidiMessage.cpp +++ b/modules/juce_audio_basics/midi/juce_MidiMessage.cpp @@ -1231,8 +1231,7 @@ struct MidiMessageTest final : public UnitTest size_t index = 0; - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS for (const auto& input : inputs) { @@ -1258,8 +1257,7 @@ struct MidiMessageTest final : public UnitTest ++index; } - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - JUCE_END_IGNORE_WARNINGS_MSVC + JUCE_END_IGNORE_DEPRECATION_WARNINGS } beginTest ("ReadVariableLengthVal should return 0 if input is truncated"); diff --git a/modules/juce_audio_devices/native/juce_Audio_ios.cpp b/modules/juce_audio_devices/native/juce_Audio_ios.cpp index e7b1c5fbe1..36f2449dfd 100644 --- a/modules/juce_audio_devices/native/juce_Audio_ios.cpp +++ b/modules/juce_audio_devices/native/juce_Audio_ios.cpp @@ -818,7 +818,7 @@ struct iOSAudioIODevice::Pimpl final : public AsyncUpdater //============================================================================== #if JUCE_MODULE_AVAILABLE_juce_graphics - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS Image getIcon (int size) { #if TARGET_OS_MACCATALYST @@ -834,7 +834,7 @@ struct iOSAudioIODevice::Pimpl final : public AsyncUpdater return {}; } - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif void switchApplication() diff --git a/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp index e45016b493..15f744ac16 100644 --- a/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp @@ -44,11 +44,10 @@ namespace juce namespace OggVorbisNamespace { #if JUCE_INCLUDE_OGGVORBIS_CODE || ! defined (JUCE_INCLUDE_OGGVORBIS_CODE) - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4305 4189 4706 4995 4365 4456 4457 4459 6297 6011 6001 6308 6255 6386 6385 6246 6387 6263 6262 28182) + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4267 4127 4244 4100 4701 4702 4013 4133 4206 4305 4189 4706 4995 4365 4456 4457 4459 6297 6011 6001 6308 6255 6386 6385 6246 6387 6263 6262 28182) JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-align", "-Wconversion", - "-Wdeprecated-declarations", "-Wdeprecated-register", "-Wfloat-conversion", "-Wfloat-equal", @@ -61,6 +60,9 @@ namespace OggVorbisNamespace "-Wswitch-default", "-Wswitch-enum", "-Wzero-as-null-pointer-constant") + + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS + JUCE_BEGIN_NO_SANITIZE ("undefined") #include "oggvorbis/vorbisenc.h" @@ -92,6 +94,7 @@ namespace OggVorbisNamespace #include "oggvorbis/libvorbis-1.3.7/lib/window.c" JUCE_END_NO_SANITIZE + JUCE_END_IGNORE_DEPRECATION_WARNINGS JUCE_END_IGNORE_WARNINGS_MSVC JUCE_END_IGNORE_WARNINGS_GCC_LIKE #else diff --git a/modules/juce_audio_processors/format_types/juce_LV2SupportLibs.cpp b/modules/juce_audio_processors/format_types/juce_LV2SupportLibs.cpp index d55c302fbf..ac718f197e 100644 --- a/modules/juce_audio_processors/format_types/juce_LV2SupportLibs.cpp +++ b/modules/juce_audio_processors/format_types/juce_LV2SupportLibs.cpp @@ -37,7 +37,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wc99-extensions", "-Wcast-align", "-Wconversion", - "-Wdeprecated-declarations", "-Wextra-semi", "-Wfloat-conversion", "-Wfloat-equal", @@ -56,7 +55,8 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wc99-extensions", "-Wswitch-enum", "-Wunused-parameter", "-Wzero-as-null-pointer-constant") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100 4200 4244 4267 4389 4702 4706 4800 4996 6308 28182 28183 6385 6386 6387 6011 6282 6323 6330 6001 6031) +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100 4200 4244 4267 4389 4702 4706 4800 6308 28182 28183 6385 6386 6387 6011 6282 6323 6330 6001 6031) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS extern "C" { @@ -113,5 +113,6 @@ extern "C" } // extern "C" +JUCE_END_IGNORE_DEPRECATION_WARNINGS JUCE_END_IGNORE_WARNINGS_MSVC JUCE_END_IGNORE_WARNINGS_GCC_LIKE diff --git a/modules/juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp b/modules/juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp index 380abce17b..977206dc07 100644 --- a/modules/juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp +++ b/modules/juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp @@ -35,8 +35,7 @@ namespace juce { -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS class LegacyAudioParameter final : public HostedAudioProcessorParameter { @@ -219,7 +218,6 @@ private: bool legacyParamIDs = false, usingManagedParameters = false; }; -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS } // namespace juce diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 7afd89b9e6..41cddcc03c 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -1932,7 +1932,7 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginWindow) }; -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) // warning about overriding deprecated methods +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS //============================================================================== static bool hasARAExtension (IPluginFactory* pluginFactory, const String& pluginClassName) @@ -3891,7 +3891,7 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginInstance) }; -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS //============================================================================== tresult VST3HostContext::beginEdit (Vst::ParamID paramID) diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index 08b48f1f32..0a8f485642 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -63,11 +63,11 @@ struct AEffect; #include "juce_VSTCommon.h" -JUCE_END_IGNORE_WARNINGS_GCC_LIKE JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS #include "juce_VSTMidiEventList.h" @@ -828,8 +828,6 @@ private: static const int defaultVSTSampleRateValue = 44100; static const int defaultVSTBlockSizeValue = 512; -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) - class TempChannelPointers { public: @@ -3446,8 +3444,6 @@ private: }; #endif -JUCE_END_IGNORE_WARNINGS_MSVC - //============================================================================== AudioProcessorEditor* VSTPluginInstance::createEditor() { @@ -3762,7 +3758,7 @@ void VSTPluginFormat::aboutToScanVSTShellPlugin (const PluginDescription&) {} } // namespace juce -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS JUCE_END_IGNORE_WARNINGS_MSVC #endif diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index b9fe2031c1..740eb051cf 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -1271,8 +1271,7 @@ VST3ClientExtensions* AudioProcessor::getVST3ClientExtensions() } //============================================================================== -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS void AudioProcessor::setParameterNotifyingHost (int parameterIndex, float newValue) { @@ -1510,8 +1509,7 @@ AudioProcessorParameter* AudioProcessor::getParamChecked (int index) const bool AudioProcessor::canAddBus ([[maybe_unused]] bool isInput) const { return false; } bool AudioProcessor::canRemoveBus ([[maybe_unused]] bool isInput) const { return false; } -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS //============================================================================== void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} diff --git a/modules/juce_core/containers/juce_Variant.cpp b/modules/juce_core/containers/juce_Variant.cpp index 0c6b6728d0..0b45553bef 100644 --- a/modules/juce_core/containers/juce_Variant.cpp +++ b/modules/juce_core/containers/juce_Variant.cpp @@ -896,13 +896,11 @@ var::NativeFunctionArgs::NativeFunctionArgs (const var& t, const var* args, int //============================================================================== #if JUCE_ALLOW_STATIC_NULL_VARIABLES -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS const var var::null; -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif diff --git a/modules/juce_core/files/juce_DirectoryIterator.cpp b/modules/juce_core/files/juce_DirectoryIterator.cpp index 4ea33f5377..782636472c 100644 --- a/modules/juce_core/files/juce_DirectoryIterator.cpp +++ b/modules/juce_core/files/juce_DirectoryIterator.cpp @@ -58,8 +58,7 @@ bool DirectoryIterator::next() return next (nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); } -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS bool DirectoryIterator::next (bool* isDirResult, bool* isHiddenResult, int64* fileSize, Time* modTime, Time* creationTime, bool* isReadOnly) @@ -144,8 +143,7 @@ bool DirectoryIterator::next (bool* isDirResult, bool* isHiddenResult, int64* fi } } -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS const File& DirectoryIterator::getFile() const { diff --git a/modules/juce_core/files/juce_File.cpp b/modules/juce_core/files/juce_File.cpp index ebc5a95796..4fd9e4288c 100644 --- a/modules/juce_core/files/juce_File.cpp +++ b/modules/juce_core/files/juce_File.cpp @@ -1029,13 +1029,11 @@ File File::getLinkedTarget() const //============================================================================== #if JUCE_ALLOW_STATIC_NULL_VARIABLES -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS const File File::nonexistent{}; -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif diff --git a/modules/juce_core/files/juce_RangedDirectoryIterator.cpp b/modules/juce_core/files/juce_RangedDirectoryIterator.cpp index cc4b7b4a98..181bd01a77 100644 --- a/modules/juce_core/files/juce_RangedDirectoryIterator.cpp +++ b/modules/juce_core/files/juce_RangedDirectoryIterator.cpp @@ -35,8 +35,7 @@ namespace juce { -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS float DirectoryEntry::getEstimatedProgress() const { @@ -85,7 +84,6 @@ void RangedDirectoryIterator::increment() iterator = nullptr; } -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS } // namespace juce diff --git a/modules/juce_core/files/juce_RangedDirectoryIterator.h b/modules/juce_core/files/juce_RangedDirectoryIterator.h index 1f6ca54483..ef9b6d2234 100644 --- a/modules/juce_core/files/juce_RangedDirectoryIterator.h +++ b/modules/juce_core/files/juce_RangedDirectoryIterator.h @@ -36,8 +36,7 @@ namespace juce { //============================================================================== -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS /** Describes the attributes of a file or folder. @@ -196,7 +195,6 @@ inline RangedDirectoryIterator begin (const RangedDirectoryIterator& it) { retur inline RangedDirectoryIterator end (const RangedDirectoryIterator&) { return {}; } -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS } // namespace juce diff --git a/modules/juce_core/memory/juce_ScopedPointer.h b/modules/juce_core/memory/juce_ScopedPointer.h index f16afbb522..107190bff1 100644 --- a/modules/juce_core/memory/juce_ScopedPointer.h +++ b/modules/juce_core/memory/juce_ScopedPointer.h @@ -46,8 +46,7 @@ class [[deprecated]] ScopedPointer { public: //============================================================================== - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS inline ScopedPointer() {} @@ -154,13 +153,11 @@ private: ScopedPointer& operator= (const ScopedPointer&) = delete; #endif - JUCE_END_IGNORE_WARNINGS_MSVC - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS }; //============================================================================== -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS template bool operator== (ObjectType1* pointer1, const ScopedPointer& pointer2) noexcept @@ -228,8 +225,7 @@ template void deleteAndZero (ScopedPointer&) { static_assert (sizeof (Type) == 12345, "Attempt to call deleteAndZero() on a ScopedPointer"); } -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS } // namespace juce diff --git a/modules/juce_core/native/juce_AndroidDocument_android.cpp b/modules/juce_core/native/juce_AndroidDocument_android.cpp index 6282ea6a67..fd8e7cabb4 100644 --- a/modules/juce_core/native/juce_AndroidDocument_android.cpp +++ b/modules/juce_core/native/juce_AndroidDocument_android.cpp @@ -231,12 +231,10 @@ struct AndroidDocumentDetail struct DirectoryIteratorEngine { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS DirectoryIteratorEngine (const File& dir, bool recursive) : iterator (dir, recursive, "*", File::findFilesAndDirectories) {} - JUCE_END_IGNORE_WARNINGS_MSVC - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS auto read() const { return AndroidDocument::fromFile (iterator.getFile()); } bool increment() { return iterator.next(); } diff --git a/modules/juce_core/native/juce_Files_mac.mm b/modules/juce_core/native/juce_Files_mac.mm index dc39f8c54e..3e81d843bf 100644 --- a/modules/juce_core/native/juce_Files_mac.mm +++ b/modules/juce_core/native/juce_Files_mac.mm @@ -431,7 +431,7 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, [[maybe_unused return true; } - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS NSMutableDictionary* dict = [[NSMutableDictionary new] autorelease]; @@ -443,7 +443,7 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, [[maybe_unused configuration: dict error: nil]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS } if (file.exists()) diff --git a/modules/juce_core/native/juce_Files_windows.cpp b/modules/juce_core/native/juce_Files_windows.cpp index bbfe611c29..8db0a1ff91 100644 --- a/modules/juce_core/native/juce_Files_windows.cpp +++ b/modules/juce_core/native/juce_Files_windows.cpp @@ -253,12 +253,12 @@ namespace WindowsFileHelpers //============================================================================== #if JUCE_ALLOW_STATIC_NULL_VARIABLES -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS const juce_wchar File::separator = '\\'; const StringRef File::separatorString ("\\"); -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif diff --git a/modules/juce_core/native/juce_SharedCode_posix.h b/modules/juce_core/native/juce_SharedCode_posix.h index c53ef0fff6..3f505f09c0 100644 --- a/modules/juce_core/native/juce_SharedCode_posix.h +++ b/modules/juce_core/native/juce_SharedCode_posix.h @@ -113,12 +113,12 @@ static MaxNumFileHandlesInitialiser maxNumFileHandlesInitialiser; //============================================================================== #if JUCE_ALLOW_STATIC_NULL_VARIABLES -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS const juce_wchar File::separator = '/'; const StringRef File::separatorString ("/"); -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif diff --git a/modules/juce_core/system/juce_CompilerWarnings.h b/modules/juce_core/system/juce_CompilerWarnings.h index 9bd8f5abea..1f49054797 100644 --- a/modules/juce_core/system/juce_CompilerWarnings.h +++ b/modules/juce_core/system/juce_CompilerWarnings.h @@ -238,6 +238,14 @@ #define JUCE_SANITIZER_ATTRIBUTE_MINIMUM_CLANG_VERSION 9 #endif +#define JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS \ + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") \ + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) + +#define JUCE_END_IGNORE_DEPRECATION_WARNINGS \ + JUCE_END_IGNORE_WARNINGS_MSVC \ + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + /** Disable sanitizers for a range of functions. This functionality doesn't seem to exist on GCC yet, so at the moment this only works for clang. diff --git a/modules/juce_core/text/juce_String.cpp b/modules/juce_core/text/juce_String.cpp index ed06d7c93a..d85cfd3009 100644 --- a/modules/juce_core/text/juce_String.cpp +++ b/modules/juce_core/text/juce_String.cpp @@ -1860,7 +1860,7 @@ String String::formattedRaw (const char* pf, ...) va_start (args, pf); #if JUCE_WINDOWS - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS #endif #if JUCE_ANDROID @@ -1881,7 +1881,7 @@ String String::formattedRaw (const char* pf, ...) #endif #if JUCE_WINDOWS - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif va_end (args); @@ -2342,13 +2342,11 @@ static String serialiseDouble (double input, int maxDecimalPlaces = 0) //============================================================================== #if JUCE_ALLOW_STATIC_NULL_VARIABLES -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS const String String::empty; -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif diff --git a/modules/juce_data_structures/values/juce_ValueTree.cpp b/modules/juce_data_structures/values/juce_ValueTree.cpp index e534747f14..b0df154664 100644 --- a/modules/juce_data_structures/values/juce_ValueTree.cpp +++ b/modules/juce_data_structures/values/juce_ValueTree.cpp @@ -1111,13 +1111,11 @@ void ValueTree::Listener::valueTreeRedirected (ValueTree&) //============================================================================== #if JUCE_ALLOW_STATIC_NULL_VARIABLES -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS const ValueTree ValueTree::invalid; -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif diff --git a/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp b/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp index a322b739e4..533a75e308 100644 --- a/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp +++ b/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp @@ -149,11 +149,9 @@ void ChildProcessCoordinator::handleConnectionLost() {} void ChildProcessCoordinator::handleMessageFromWorker (const MemoryBlock& mb) { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS handleMessageFromSlave (mb); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - JUCE_END_IGNORE_WARNINGS_MSVC + JUCE_END_IGNORE_DEPRECATION_WARNINGS } bool ChildProcessCoordinator::sendMessageToWorker (const MemoryBlock& mb) @@ -276,11 +274,9 @@ void ChildProcessWorker::handleConnectionLost() {} void ChildProcessWorker::handleMessageFromCoordinator (const MemoryBlock& mb) { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS handleMessageFromMaster (mb); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - JUCE_END_IGNORE_WARNINGS_MSVC + JUCE_END_IGNORE_DEPRECATION_WARNINGS } bool ChildProcessWorker::sendMessageToCoordinator (const MemoryBlock& mb) diff --git a/modules/juce_events/native/juce_MessageManager_mac.mm b/modules/juce_events/native/juce_MessageManager_mac.mm index f727616ae9..0777966433 100644 --- a/modules/juce_events/native/juce_MessageManager_mac.mm +++ b/modules/juce_events/native/juce_MessageManager_mac.mm @@ -142,11 +142,11 @@ struct AppDelegateClass final : public ObjCClass { if (notification.userInfo != nil) { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion") if (userNotification != nil && userNotification.userInfo != nil) diff --git a/modules/juce_graphics/fonts/juce_Font.cpp b/modules/juce_graphics/fonts/juce_Font.cpp index 5641606f1b..b6553ed19e 100644 --- a/modules/juce_graphics/fonts/juce_Font.cpp +++ b/modules/juce_graphics/fonts/juce_Font.cpp @@ -754,11 +754,9 @@ float Font::getDescentInPoints() const { return getDescent() * getHeightToP int Font::getStringWidth (const String& text) const { - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS return (int) std::ceil (getStringWidthFloat (text)); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - JUCE_END_IGNORE_WARNINGS_MSVC + JUCE_END_IGNORE_DEPRECATION_WARNINGS } float Font::getStringWidthFloat (const String& text) const @@ -941,11 +939,9 @@ public: beginTest ("Old constructor from Typeface"); { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS Font f { face }; - JUCE_END_IGNORE_WARNINGS_MSVC - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS expect (f.getTypefaceName() == face->getName()); expect (f.getTypefaceStyle() == face->getStyle()); diff --git a/modules/juce_graphics/images/juce_Image.cpp b/modules/juce_graphics/images/juce_Image.cpp index 112bcce29e..277ebd6bf0 100644 --- a/modules/juce_graphics/images/juce_Image.cpp +++ b/modules/juce_graphics/images/juce_Image.cpp @@ -841,13 +841,11 @@ void ImageEffects::applySingleChannelBoxBlurEffect (int radius, const Image& inp //============================================================================== #if JUCE_ALLOW_STATIC_NULL_VARIABLES -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS const Image Image::null; -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif diff --git a/modules/juce_graphics/juce_graphics_Harfbuzz.cpp b/modules/juce_graphics/juce_graphics_Harfbuzz.cpp index bc464f9fb8..2653786c7e 100644 --- a/modules/juce_graphics/juce_graphics_Harfbuzz.cpp +++ b/modules/juce_graphics/juce_graphics_Harfbuzz.cpp @@ -34,10 +34,9 @@ #include -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100 4127 4189 4244 4245 4265 4267 4309 4310 4312 4456 4457 4458 4459 4701 4702 4706 4996 6001 6011 6239 6244 6246 6262 6297 6313 6319 6326 6336 6385 6386 28251) +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100 4127 4189 4244 4245 4265 4267 4309 4310 4312 4456 4457 4458 4459 4701 4702 4706 6001 6011 6239 6244 6246 6262 6297 6313 6319 6326 6336 6385 6386 28251) -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", - "-Wcast-function-type", +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type", "-Wsign-conversion", "-Wzero-as-null-pointer-constant", "-Wformat-pedantic", @@ -56,6 +55,8 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", "-Woverflow", "-Wimplicit-fallthrough") +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS + #define HAVE_ATEXIT 1 #if JUCE_LINUX || JUCE_BSD @@ -95,5 +96,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", #undef HAVE_FREETYPE #undef HAVE_CORETEXT +JUCE_END_IGNORE_DEPRECATION_WARNINGS JUCE_END_IGNORE_WARNINGS_GCC_LIKE JUCE_END_IGNORE_WARNINGS_MSVC diff --git a/modules/juce_gui_basics/native/juce_FileChooser_mac.mm b/modules/juce_gui_basics/native/juce_FileChooser_mac.mm index 4987e5f2bb..24c84c8129 100644 --- a/modules/juce_gui_basics/native/juce_FileChooser_mac.mm +++ b/modules/juce_gui_basics/native/juce_FileChooser_mac.mm @@ -103,9 +103,9 @@ public: [panel setTitle: nsTitle]; [panel setReleasedWhenClosed: YES]; - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS [panel setAllowedFileTypes: createAllowedTypesArray (filters)]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS if (! isSave) { diff --git a/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm b/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm index 86ea738548..664243a03a 100644 --- a/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm +++ b/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm @@ -1460,9 +1460,9 @@ public: if (@available (macOS 10.13, *)) return NSPasteboardTypeFileURL; - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS return (NSString*) kUTTypeFileURL; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS }(); return [NSArray arrayWithObjects: type, (NSString*) kPasteboardTypeFileURLPromise, NSPasteboardTypeString, nil]; diff --git a/modules/juce_gui_basics/native/juce_PerScreenDisplayLinks_mac.h b/modules/juce_gui_basics/native/juce_PerScreenDisplayLinks_mac.h index 4e412beb13..dcea884cf9 100644 --- a/modules/juce_gui_basics/native/juce_PerScreenDisplayLinks_mac.h +++ b/modules/juce_gui_basics/native/juce_PerScreenDisplayLinks_mac.h @@ -109,7 +109,7 @@ private: // NSScreen.displayLink(target:selector:) all of which were only introduced in macOS 14+ however, // it's not clear how these methods can be used to replace all use cases -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS class ScopedDisplayLink { @@ -188,7 +188,7 @@ private: JUCE_DECLARE_NON_MOVEABLE (ScopedDisplayLink) }; -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS //============================================================================== /* diff --git a/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm b/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm index e2d229f2a2..2d7835d82b 100644 --- a/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm +++ b/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm @@ -115,9 +115,9 @@ static UIInterfaceOrientation getWindowOrientation() return [(UIWindowScene*) scene interfaceOrientation]; } - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS return [sharedApplication statusBarOrientation]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS } struct Orientations diff --git a/modules/juce_gui_basics/native/juce_Windowing_mac.mm b/modules/juce_gui_basics/native/juce_Windowing_mac.mm index 6b18e9de82..9839e5dd8f 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_mac.mm +++ b/modules/juce_gui_basics/native/juce_Windowing_mac.mm @@ -603,12 +603,12 @@ static Image createNSWindowSnapshot (NSWindow* nsWindow) #else - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS return createImageFromCGImage ((CGImageRef) CFAutorelease (CGWindowListCreateImage (CGRectNull, kCGWindowListOptionIncludingWindow, (CGWindowID) [nsWindow windowNumber], kCGWindowImageBoundsIgnoreFraming))); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif } diff --git a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp index 4a83998c50..f9aa7a9b56 100644 --- a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp +++ b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp @@ -1697,11 +1697,9 @@ void CodeEditorComponent::setFont (const Font& newFont) { font = newFont; - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS charWidth = font.getStringWidthFloat ("0"); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - JUCE_END_IGNORE_WARNINGS_MSVC + JUCE_END_IGNORE_DEPRECATION_WARNINGS lineHeight = roundToInt (font.getHeight()); resized(); diff --git a/modules/juce_gui_extra/native/juce_AppleRemote_mac.mm b/modules/juce_gui_extra/native/juce_AppleRemote_mac.mm index 0b2f5493d7..77d3f43a7e 100644 --- a/modules/juce_gui_extra/native/juce_AppleRemote_mac.mm +++ b/modules/juce_gui_extra/native/juce_AppleRemote_mac.mm @@ -63,9 +63,9 @@ namespace return kIOMainPortDefault; #endif - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS return kIOMasterPortDefault; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS }(); if (IOServiceGetMatchingServices (defaultPort, dict, &iter) == kIOReturnSuccess diff --git a/modules/juce_gui_extra/native/juce_PushNotifications_mac.cpp b/modules/juce_gui_extra/native/juce_PushNotifications_mac.cpp index b1a3c81e83..c994beb3bd 100644 --- a/modules/juce_gui_extra/native/juce_PushNotifications_mac.cpp +++ b/modules/juce_gui_extra/native/juce_PushNotifications_mac.cpp @@ -35,7 +35,7 @@ namespace juce { -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS namespace PushNotificationsDelegateDetailsOsx { @@ -534,6 +534,6 @@ private: PushNotifications::Settings settings; }; -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS } // namespace juce diff --git a/modules/juce_gui_extra/native/juce_SystemTrayIcon_mac.cpp b/modules/juce_gui_extra/native/juce_SystemTrayIcon_mac.cpp index cad7a62fa1..624282de74 100644 --- a/modules/juce_gui_extra/native/juce_SystemTrayIcon_mac.cpp +++ b/modules/juce_gui_extra/native/juce_SystemTrayIcon_mac.cpp @@ -35,7 +35,7 @@ namespace juce { -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId, int topLevelIndex, bool addDelegate); @@ -446,6 +446,6 @@ void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu) pimpl->statusItemHolder->showMenu (menu); } -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS } // namespace juce diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp b/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp index f92dfbe4e6..ffea19caa4 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp @@ -1137,11 +1137,11 @@ private: // Using the non-deprecated webkit_javascript_result_get_js_value() functions seems easier // but returned values fail the JS_IS_VALUE() internal assertion. The example code from the // documentation doesn't seem to work either. - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS WebKitJavascriptResultUniquePtr jsResult { wk.juce_webkit_web_view_run_javascript_finish (owner->webview, result, &error) }; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS if (jsResult == nullptr) { diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm b/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm index e00ebde534..fcff49f51b 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm @@ -197,9 +197,9 @@ struct WebViewKeyEquivalentResponder final : public ObjCClass if (@available (macOS 10.12, *)) return (modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagCommand; - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS return (modifierFlags & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS }(); if (isCommandDown) @@ -270,7 +270,7 @@ struct WebViewKeyEquivalentResponder final : public ObjCClass } }; -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS struct DownloadClickDetectorClass final : public ObjCClass { DownloadClickDetectorClass() : ObjCClass ("JUCEWebClickDetector_") @@ -372,7 +372,7 @@ private: } } }; -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif // Connects the delegate to the rest of the implementation without making WebViewDelegateClass @@ -652,7 +652,7 @@ window.__JUCE__ = { //============================================================================== #if JUCE_MAC -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS class WebBrowserComponent::Impl::Platform::WebViewImpl : public WebBrowserComponent::Impl::PlatformInterface, #if JUCE_MAC public NSViewComponent @@ -783,7 +783,7 @@ private: ObjCObjectHandle webView; ObjCObjectHandle clickListener; }; -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS #endif class WebBrowserComponent::Impl::Platform::WKWebViewImpl : public WebBrowserComponent::Impl::PlatformInterface, diff --git a/modules/juce_opengl/native/juce_OpenGL_mac.h b/modules/juce_opengl/native/juce_OpenGL_mac.h index badc06f207..c5efcc0e87 100644 --- a/modules/juce_opengl/native/juce_OpenGL_mac.h +++ b/modules/juce_opengl/native/juce_OpenGL_mac.h @@ -35,7 +35,7 @@ namespace juce { -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS class OpenGLContext::NativeContext { @@ -326,6 +326,6 @@ bool OpenGLHelpers::isContextActive() return CGLGetCurrentContext() != CGLContextObj(); } -JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_DEPRECATION_WARNINGS } // namespace juce diff --git a/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp b/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp index 76f44dcd50..478db67fb2 100644 --- a/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp +++ b/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp @@ -328,8 +328,7 @@ String OnlineUnlockStatus::MachineIDUtilities::getUniqueMachineID() return getEncodedIDString (SystemStats::getUniqueDeviceID()); } -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) +JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS StringArray OnlineUnlockStatus::MachineIDUtilities::getLocalMachineIDs() { @@ -350,8 +349,7 @@ StringArray OnlineUnlockStatus::getLocalMachineIDs() return MachineIDUtilities::getLocalMachineIDs(); } -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_DEPRECATION_WARNINGS void OnlineUnlockStatus::userCancelled() { diff --git a/modules/juce_video/native/juce_CameraDevice_mac.h b/modules/juce_video/native/juce_CameraDevice_mac.h index 4a5dc50965..df409217db 100644 --- a/modules/juce_video/native/juce_CameraDevice_mac.h +++ b/modules/juce_video/native/juce_CameraDevice_mac.h @@ -148,9 +148,9 @@ struct CameraDevice::Pimpl { if (@available (macOS 10.15, *)) { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS const auto deviceType = AVCaptureDeviceTypeExternalUnknown; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS auto* discovery = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes: @[AVCaptureDeviceTypeBuiltInWideAngleCamera, deviceType] mediaType: AVMediaTypeVideo @@ -159,9 +159,9 @@ struct CameraDevice::Pimpl return [discovery devices]; } - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS return [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS } static StringArray getAvailableDevices() @@ -331,7 +331,7 @@ private: NSUniquePtr> delegate; }; - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS class PreCatalinaStillImageOutput : public ImageOutputBase { public: @@ -397,7 +397,7 @@ private: private: AVCaptureStillImageOutput* imageOutput = nil; }; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_DEPRECATION_WARNINGS //============================================================================== void addImageCapture() From 04188c0e09edf182d7e208cfcf1c9ba61cbd8e82 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 13 Oct 2023 18:24:42 +0100 Subject: [PATCH 032/557] AudioBuffer: Remove approximatelyEqual --- .../buffers/juce_AudioSampleBuffer.h | 155 ++++++------------ 1 file changed, 48 insertions(+), 107 deletions(-) diff --git a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h index cfc841150f..ddd5889673 100644 --- a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -700,14 +700,10 @@ public: jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - if (! approximatelyEqual (gain, Type (1)) && ! isClear) + if (! isClear) { auto* d = channels[channel] + startSample; - - if (approximatelyEqual (gain, Type())) - FloatVectorOperations::clear (d, numSamples); - else - FloatVectorOperations::multiply (d, gain, numSamples); + FloatVectorOperations::multiply (d, gain, numSamples); } } @@ -742,23 +738,16 @@ public: { if (! isClear) { - if (approximatelyEqual (startGain, endGain)) - { - applyGain (channel, startSample, numSamples, startGain); - } - else - { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - const auto increment = (endGain - startGain) / (float) numSamples; - auto* d = channels[channel] + startSample; + const auto increment = (endGain - startGain) / (float) numSamples; + auto* d = channels[channel] + startSample; - while (--numSamples >= 0) - { - *d++ *= startGain; - startGain += increment; - } + while (--numSamples >= 0) + { + *d++ *= startGain; + startGain += increment; } } } @@ -812,7 +801,7 @@ public: jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); - if (! approximatelyEqual (gainToApplyToSource, (Type) 0) && numSamples > 0 && ! source.isClear) + if (numSamples > 0 && ! source.isClear) { auto* d = channels[destChannel] + destStartSample; auto* s = source.channels[sourceChannel] + sourceStartSample; @@ -822,18 +811,11 @@ public: if (isClear) { isClear = false; - - if (! approximatelyEqual (gainToApplyToSource, Type (1))) - FloatVectorOperations::copyWithMultiply (d, s, gainToApplyToSource, numSamples); - else - FloatVectorOperations::copy (d, s, numSamples); + FloatVectorOperations::copyWithMultiply (d, s, gainToApplyToSource, numSamples); } else { - if (! approximatelyEqual (gainToApplyToSource, Type (1))) - FloatVectorOperations::addWithMultiply (d, s, gainToApplyToSource, numSamples); - else - FloatVectorOperations::add (d, s, numSamples); + FloatVectorOperations::addWithMultiply (d, s, gainToApplyToSource, numSamples); } JUCE_END_IGNORE_WARNINGS_MSVC @@ -864,26 +846,16 @@ public: jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); - if (! approximatelyEqual (gainToApplyToSource, Type()) && numSamples > 0) + auto* d = channels[destChannel] + destStartSample; + + if (isClear) { - auto* d = channels[destChannel] + destStartSample; - - if (isClear) - { - isClear = false; - - if (! approximatelyEqual (gainToApplyToSource, Type (1))) - FloatVectorOperations::copyWithMultiply (d, source, gainToApplyToSource, numSamples); - else - FloatVectorOperations::copy (d, source, numSamples); - } - else - { - if (! approximatelyEqual (gainToApplyToSource, Type (1))) - FloatVectorOperations::addWithMultiply (d, source, gainToApplyToSource, numSamples); - else - FloatVectorOperations::add (d, source, numSamples); - } + isClear = false; + FloatVectorOperations::copyWithMultiply (d, source, gainToApplyToSource, numSamples); + } + else + { + FloatVectorOperations::addWithMultiply (d, source, gainToApplyToSource, numSamples); } } @@ -913,27 +885,20 @@ public: Type startGain, Type endGain) noexcept { - if (approximatelyEqual (startGain, endGain)) - { - addFrom (destChannel, destStartSample, source, numSamples, startGain); - } - else - { - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); - if (numSamples > 0) + if (numSamples > 0) + { + isClear = false; + const auto increment = (endGain - startGain) / (Type) numSamples; + auto* d = channels[destChannel] + destStartSample; + + while (--numSamples >= 0) { - isClear = false; - const auto increment = (endGain - startGain) / (Type) numSamples; - auto* d = channels[destChannel] + destStartSample; - - while (--numSamples >= 0) - { - *d++ += startGain * *source++; - startGain += increment; - } + *d++ += startGain * *source++; + startGain += increment; } } } @@ -1036,25 +1001,8 @@ public: if (numSamples > 0) { auto* d = channels[destChannel] + destStartSample; - - if (! approximatelyEqual (gain, Type (1))) - { - if (approximatelyEqual (gain, Type())) - { - if (! isClear) - FloatVectorOperations::clear (d, numSamples); - } - else - { - isClear = false; - FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); - } - } - else - { - isClear = false; - FloatVectorOperations::copy (d, source, numSamples); - } + isClear = false; + FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); } } @@ -1085,27 +1033,20 @@ public: Type startGain, Type endGain) noexcept { - if (approximatelyEqual (startGain, endGain)) - { - copyFrom (destChannel, destStartSample, source, numSamples, startGain); - } - else - { - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); - if (numSamples > 0) + if (numSamples > 0) + { + isClear = false; + const auto increment = (endGain - startGain) / (Type) numSamples; + auto* d = channels[destChannel] + destStartSample; + + while (--numSamples >= 0) { - isClear = false; - const auto increment = (endGain - startGain) / (Type) numSamples; - auto* d = channels[destChannel] + destStartSample; - - while (--numSamples >= 0) - { - *d++ = startGain * *source++; - startGain += increment; - } + *d++ = startGain * *source++; + startGain += increment; } } } From a50292f50d1f64c085b18cb3b078a85a9d52650c Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Thu, 21 Nov 2024 15:57:56 +0000 Subject: [PATCH 033/557] AudioBuffer: Prefer early returns to nested if statements --- .../buffers/juce_AudioSampleBuffer.h | 217 +++++++++--------- 1 file changed, 114 insertions(+), 103 deletions(-) diff --git a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h index ddd5889673..5fbad7aac5 100644 --- a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -569,17 +569,17 @@ public: */ void clear() noexcept { - if (! isClear) - { - for (int i = 0; i < numChannels; ++i) - { - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4661) - FloatVectorOperations::clear (channels[i], size); - JUCE_END_IGNORE_WARNINGS_MSVC - } + if (isClear) + return; - isClear = true; + for (int i = 0; i < numChannels; ++i) + { + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4661) + FloatVectorOperations::clear (channels[i], size); + JUCE_END_IGNORE_WARNINGS_MSVC } + + isClear = true; } /** Clears a specified region of all the channels. @@ -598,13 +598,13 @@ public: { jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - if (! isClear) - { - for (int i = 0; i < numChannels; ++i) - FloatVectorOperations::clear (channels[i] + startSample, numSamples); + if (isClear) + return; - isClear = (startSample == 0 && numSamples == size); - } + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::clear (channels[i] + startSample, numSamples); + + isClear = (startSample == 0 && numSamples == size); } /** Clears a specified region of just one channel. @@ -700,11 +700,11 @@ public: jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - if (! isClear) - { - auto* d = channels[channel] + startSample; - FloatVectorOperations::multiply (d, gain, numSamples); - } + if (isClear) + return; + + auto* d = channels[channel] + startSample; + FloatVectorOperations::multiply (d, gain, numSamples); } /** Applies a gain multiple to a region of all the channels. @@ -736,19 +736,19 @@ public: void applyGainRamp (int channel, int startSample, int numSamples, Type startGain, Type endGain) noexcept { - if (! isClear) + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + + if (isClear) + return; + + const auto increment = (endGain - startGain) / (float) numSamples; + auto* d = channels[channel] + startSample; + + while (--numSamples >= 0) { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - - const auto increment = (endGain - startGain) / (float) numSamples; - auto* d = channels[channel] + startSample; - - while (--numSamples >= 0) - { - *d++ *= startGain; - startGain += increment; - } + *d++ *= startGain; + startGain += increment; } } @@ -801,25 +801,25 @@ public: jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); - if (numSamples > 0 && ! source.isClear) + if (numSamples <= 0 || source.isClear) + return; + + auto* d = channels[destChannel] + destStartSample; + auto* s = source.channels[sourceChannel] + sourceStartSample; + + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4661) + + if (isClear) { - auto* d = channels[destChannel] + destStartSample; - auto* s = source.channels[sourceChannel] + sourceStartSample; - - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4661) - - if (isClear) - { - isClear = false; - FloatVectorOperations::copyWithMultiply (d, s, gainToApplyToSource, numSamples); - } - else - { - FloatVectorOperations::addWithMultiply (d, s, gainToApplyToSource, numSamples); - } - - JUCE_END_IGNORE_WARNINGS_MSVC + isClear = false; + FloatVectorOperations::copyWithMultiply (d, s, gainToApplyToSource, numSamples); } + else + { + FloatVectorOperations::addWithMultiply (d, s, gainToApplyToSource, numSamples); + } + + JUCE_END_IGNORE_WARNINGS_MSVC } /** Adds samples from an array of floats to one of the channels. @@ -846,6 +846,9 @@ public: jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); + if (numSamples <= 0) + return; + auto* d = channels[destChannel] + destStartSample; if (isClear) @@ -889,19 +892,19 @@ public: jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); - if (numSamples > 0) - { - isClear = false; - const auto increment = (endGain - startGain) / (Type) numSamples; - auto* d = channels[destChannel] + destStartSample; + if (numSamples <= 0) + return; - while (--numSamples >= 0) - { - *d++ += startGain * *source++; - startGain += increment; - } + isClear = false; + const auto increment = (endGain - startGain) / (Type) numSamples; + auto* d = channels[destChannel] + destStartSample; + + while (--numSamples >= 0) + { + *d++ += startGain * *source++; + startGain += increment; } - } +} /** Copies samples from another buffer to this one. @@ -930,20 +933,20 @@ public: jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); jassert (sourceStartSample >= 0 && numSamples >= 0 && sourceStartSample + numSamples <= source.size); - if (numSamples > 0) + if (numSamples <= 0) + return; + + if (source.isClear) { - if (source.isClear) - { - if (! isClear) - FloatVectorOperations::clear (channels[destChannel] + destStartSample, numSamples); - } - else - { - isClear = false; - FloatVectorOperations::copy (channels[destChannel] + destStartSample, - source.channels[sourceChannel] + sourceStartSample, - numSamples); - } + if (! isClear) + FloatVectorOperations::clear (channels[destChannel] + destStartSample, numSamples); + } + else + { + isClear = false; + FloatVectorOperations::copy (channels[destChannel] + destStartSample, + source.channels[sourceChannel] + sourceStartSample, + numSamples); } } @@ -968,11 +971,11 @@ public: jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); - if (numSamples > 0) - { - isClear = false; - FloatVectorOperations::copy (channels[destChannel] + destStartSample, source, numSamples); - } + if (numSamples <= 0) + return; + + isClear = false; + FloatVectorOperations::copy (channels[destChannel] + destStartSample, source, numSamples); } /** Copies samples from an array of floats into one of the channels, applying a gain to it. @@ -998,12 +1001,12 @@ public: jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); - if (numSamples > 0) - { - auto* d = channels[destChannel] + destStartSample; - isClear = false; - FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); - } + if (numSamples <= 0) + return; + + auto* d = channels[destChannel] + destStartSample; + isClear = false; + FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); } /** Copies samples from an array of floats into one of the channels, applying a gain ramp. @@ -1037,17 +1040,17 @@ public: jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); - if (numSamples > 0) - { - isClear = false; - const auto increment = (endGain - startGain) / (Type) numSamples; - auto* d = channels[destChannel] + destStartSample; + if (numSamples <= 0) + return; - while (--numSamples >= 0) - { - *d++ = startGain * *source++; - startGain += increment; - } + isClear = false; + const auto increment = (endGain - startGain) / (Type) numSamples; + auto* d = channels[destChannel] + destStartSample; + + while (--numSamples >= 0) + { + *d++ = startGain * *source++; + startGain += increment; } } @@ -1077,8 +1080,7 @@ public: if (isClear) return Type (0); - auto r = findMinMax (channel, startSample, numSamples); - + const auto r = findMinMax (channel, startSample, numSamples); return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); } @@ -1087,9 +1089,11 @@ public: { Type mag (0); - if (! isClear) - for (int i = 0; i < numChannels; ++i) - mag = jmax (mag, getMagnitude (i, startSample, numSamples)); + if (isClear) + return mag; + + for (int i = 0; i < numChannels; ++i) + mag = jmax (mag, getMagnitude (i, startSample, numSamples)); return mag; } @@ -1100,7 +1104,7 @@ public: jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - if (numSamples <= 0 || channel < 0 || channel >= numChannels || isClear) + if (numSamples <= 0 || isClear || ! isPositiveAndBelow (channel, numChannels)) return Type (0); auto* data = channels[channel] + startSample; @@ -1121,14 +1125,21 @@ public: jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - if (! isClear) - std::reverse (channels[channel] + startSample, - channels[channel] + startSample + numSamples); + if (isClear) + return; + + std::reverse (channels[channel] + startSample, + channels[channel] + startSample + numSamples); } /** Reverses a part of the buffer. */ void reverse (int startSample, int numSamples) const noexcept { + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + + if (isClear) + return; + for (int i = 0; i < numChannels; ++i) reverse (i, startSample, numSamples); } From 8fe8717ebc9ba0f3c6632f86b17044d4c936b338 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 22 Nov 2024 10:24:37 +0000 Subject: [PATCH 034/557] Random: Make the system random object safer to use from multiple threads --- modules/juce_core/maths/juce_Random.cpp | 38 ++++++++++++++++++------- modules/juce_core/maths/juce_Random.h | 13 ++++++--- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/modules/juce_core/maths/juce_Random.cpp b/modules/juce_core/maths/juce_Random.cpp index a11bd8693f..32a4d6af7b 100644 --- a/modules/juce_core/maths/juce_Random.cpp +++ b/modules/juce_core/maths/juce_Random.cpp @@ -46,26 +46,29 @@ Random::Random() : seed (1) void Random::setSeed (const int64 newSeed) noexcept { - if (this == &getSystemRandom()) - { - // Resetting the system Random risks messing up - // JUCE's internal state. If you need a predictable - // stream of random numbers you should use a local - // Random object. - jassertfalse; - return; - } + // Resetting the system Random risks messing up JUCE's internal state. + // If you need a predictable stream of random numbers you should use a + // local Random object. + jassert (! isSystemRandom); seed = newSeed; } void Random::combineSeed (const int64 seedValue) noexcept { + // Resetting the system Random risks messing up JUCE's internal state. + // Consider using a local Random object instead. + jassert (! isSystemRandom); + seed ^= nextInt64() ^ seedValue; } void Random::setSeedRandomly() { + // Resetting the system Random risks messing up JUCE's internal state. + // Consider using a local Random object instead. + jassert (! isSystemRandom); + static std::atomic globalSeed { 0 }; combineSeed (globalSeed ^ (int64) (pointer_sized_int) this); @@ -78,13 +81,28 @@ void Random::setSeedRandomly() Random& Random::getSystemRandom() noexcept { - static Random sysRand; + thread_local Random sysRand = std::invoke ([] + { + Random r; + #if JUCE_ASSERTIONS_ENABLED_OR_LOGGED + r.isSystemRandom = true; + #endif + return r; + }); + return sysRand; } //============================================================================== int Random::nextInt() noexcept { + // If you encounter this assertion you've likely stored a reference to the + // system random object and are accessing it from a thread other than the + // one it was first created on. This may lead to race conditions on the + // random object. To avoid this assertion call Random::getSystemRandom() + // directly instead of storing a reference. + jassert (! isSystemRandom || this == &getSystemRandom()); + seed = (int64) (((((uint64) seed) * 0x5deece66dLL) + 11) & 0xffffffffffffLL); return (int) (seed >> 16); diff --git a/modules/juce_core/maths/juce_Random.h b/modules/juce_core/maths/juce_Random.h index d991bea036..e495923921 100644 --- a/modules/juce_core/maths/juce_Random.h +++ b/modules/juce_core/maths/juce_Random.h @@ -128,11 +128,12 @@ public: */ void setSeedRandomly(); - /** The overhead of creating a new Random object is fairly small, but if you want to avoid - it, you can call this method to get a global shared Random object. + /** The overhead of creating a new Random object is fairly small, but if you want + to avoid it, you can call this method to get a global shared Random object. - It's not thread-safe though, so threads should use their own Random object, otherwise - you run the risk of your random numbers becoming.. erm.. randomly corrupted.. + Note this will return a different object per thread it's accessed from, + making it thread safe. However, it's therefore important not store a reference + to this object that will later be accessed from other threads. */ static Random& getSystemRandom() noexcept; @@ -140,6 +141,10 @@ private: //============================================================================== int64 seed; + #if JUCE_ASSERTIONS_ENABLED_OR_LOGGED + bool isSystemRandom = false; + #endif + JUCE_LEAK_DETECTOR (Random) }; From 655d18b7210335ad4d6d3fb94d96a4520c24fde8 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 22 Nov 2024 14:40:06 +0000 Subject: [PATCH 035/557] Random: Add some extra data race tests --- modules/juce_core/maths/juce_Random.cpp | 94 +++++++++++++++++++++---- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/modules/juce_core/maths/juce_Random.cpp b/modules/juce_core/maths/juce_Random.cpp index 32a4d6af7b..ac5a82c943 100644 --- a/modules/juce_core/maths/juce_Random.cpp +++ b/modules/juce_core/maths/juce_Random.cpp @@ -204,22 +204,92 @@ public: void runTest() override { beginTest ("Random"); - - Random r = getRandom(); - - for (int i = 2000; --i >= 0;) { - expect (r.nextDouble() >= 0.0 && r.nextDouble() < 1.0); - expect (r.nextFloat() >= 0.0f && r.nextFloat() < 1.0f); - expect (r.nextInt (5) >= 0 && r.nextInt (5) < 5); - expect (r.nextInt (1) == 0); + Random r = getRandom(); - int n = r.nextInt (50) + 1; - expect (r.nextInt (n) >= 0 && r.nextInt (n) < n); + for (int i = 2000; --i >= 0;) + { + expect (r.nextDouble() >= 0.0 && r.nextDouble() < 1.0); + expect (r.nextFloat() >= 0.0f && r.nextFloat() < 1.0f); + expect (r.nextInt (5) >= 0 && r.nextInt (5) < 5); + expect (r.nextInt (1) == 0); - n = r.nextInt (0x7ffffffe) + 1; - expect (r.nextInt (n) >= 0 && r.nextInt (n) < n); + int n = r.nextInt (50) + 1; + expect (r.nextInt (n) >= 0 && r.nextInt (n) < n); + + n = r.nextInt (0x7ffffffe) + 1; + expect (r.nextInt (n) >= 0 && r.nextInt (n) < n); + } } + + beginTest ("System random stress test"); + { + // Run this with thread-sanitizer to detect race conditions + runOnMultipleThreadsConcurrently ([] { Random::getSystemRandom().nextInt(); }); + } + } + +private: + static void runOnMultipleThreadsConcurrently (std::function functionToInvoke, + int numberOfInvocationsPerThread = 10'000, + int numberOfThreads = 100) + { + class FastWaitableEvent + { + public: + void notify() { notified = true; } + void wait() const { while (! notified){} } + + private: + std::atomic notified = false; + }; + + class InvokerThread final : private Thread + { + public: + InvokerThread (std::function fn, FastWaitableEvent& notificationEvent, int numInvocationsToTrigger) + : Thread ("InvokerThread"), + invokable (fn), + notified (¬ificationEvent), + numInvocations (numInvocationsToTrigger) + { + startThread(); + } + + ~InvokerThread() { stopThread (-1); } + + void waitUntilReady() const { ready.wait(); } + + private: + void run() final + { + ready.notify(); + notified->wait(); + + for (int i = numInvocations; --i >= 0;) + invokable(); + } + + std::function invokable; + FastWaitableEvent* notified; + FastWaitableEvent ready; + int numInvocations; + }; + + std::vector> threads; + threads.reserve ((size_t) numberOfThreads); + FastWaitableEvent start; + + for (int i = numberOfThreads; --i >= 0;) + threads.push_back (std::make_unique (functionToInvoke, start, numberOfInvocationsPerThread)); + + for (auto& thread : threads) + thread->waitUntilReady(); + + // just to increase the odds that all the threads are now at the same point + // ready to be notified + Thread::sleep (1); + start.notify(); } }; From f98bf8434a1762b81d9add84d85dc34ae3ed694a Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 22 Nov 2024 10:26:04 +0000 Subject: [PATCH 036/557] TemporaryFile: Stopping use a LockedRandom now that system random is thread safe --- .../juce_core/files/juce_TemporaryFile.cpp | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/modules/juce_core/files/juce_TemporaryFile.cpp b/modules/juce_core/files/juce_TemporaryFile.cpp index 52fbc58839..6614054b62 100644 --- a/modules/juce_core/files/juce_TemporaryFile.cpp +++ b/modules/juce_core/files/juce_TemporaryFile.cpp @@ -35,23 +35,6 @@ namespace juce { -// Using Random::getSystemRandom() can be a bit dangerous in multithreaded contexts! -class LockedRandom -{ -public: - int nextInt() - { - const ScopedLock lock (mutex); - return random.nextInt(); - } - -private: - CriticalSection mutex; - Random random; -}; - -static LockedRandom lockedRandom; - static File createTempFile (const File& parentDirectory, String name, const String& suffix, int optionFlags) { @@ -63,7 +46,7 @@ static File createTempFile (const File& parentDirectory, String name, TemporaryFile::TemporaryFile (const String& suffix, const int optionFlags) : temporaryFile (createTempFile (File::getSpecialLocation (File::tempDirectory), - "temp_" + String::toHexString (lockedRandom.nextInt()), + "temp_" + String::toHexString (Random::getSystemRandom().nextInt()), suffix, optionFlags)), targetFile() { @@ -72,7 +55,7 @@ TemporaryFile::TemporaryFile (const String& suffix, const int optionFlags) TemporaryFile::TemporaryFile (const File& target, const int optionFlags) : temporaryFile (createTempFile (target.getParentDirectory(), target.getFileNameWithoutExtension() - + "_temp" + String::toHexString (lockedRandom.nextInt()), + + "_temp" + String::toHexString (Random::getSystemRandom().nextInt()), target.getFileExtension(), optionFlags)), targetFile (target) { From 48375432be49d1c605d8d9db7f7cf574f9ee84a0 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 13 Oct 2023 17:23:08 +0100 Subject: [PATCH 037/557] TemporaryFile: Make single argument constructors explicit --- .../juce_core/files/juce_TemporaryFile.cpp | 6 ++++ modules/juce_core/files/juce_TemporaryFile.h | 32 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/modules/juce_core/files/juce_TemporaryFile.cpp b/modules/juce_core/files/juce_TemporaryFile.cpp index 6614054b62..91a049661b 100644 --- a/modules/juce_core/files/juce_TemporaryFile.cpp +++ b/modules/juce_core/files/juce_TemporaryFile.cpp @@ -44,6 +44,12 @@ static File createTempFile (const File& parentDirectory, String name, return parentDirectory.getNonexistentChildFile (name, suffix, (optionFlags & TemporaryFile::putNumbersInBrackets) != 0); } +TemporaryFile::TemporaryFile() : TemporaryFile { String{} } {} + +TemporaryFile::TemporaryFile (const String& suffix) : TemporaryFile { suffix, 0 } {} + +TemporaryFile::TemporaryFile (const File& target) : TemporaryFile { target, 0 } {} + TemporaryFile::TemporaryFile (const String& suffix, const int optionFlags) : temporaryFile (createTempFile (File::getSpecialLocation (File::tempDirectory), "temp_" + String::toHexString (Random::getSystemRandom().nextInt()), diff --git a/modules/juce_core/files/juce_TemporaryFile.h b/modules/juce_core/files/juce_TemporaryFile.h index 33377f7c12..dffe03dbd7 100644 --- a/modules/juce_core/files/juce_TemporaryFile.h +++ b/modules/juce_core/files/juce_TemporaryFile.h @@ -87,6 +87,16 @@ public: }; //============================================================================== + /** Creates a randomly-named temporary file in the default temp directory. + */ + TemporaryFile(); + + /** Creates a randomly-named temporary file in the default temp directory. + + @param suffix a file suffix to use for the file + */ + explicit TemporaryFile (const String& suffix); + /** Creates a randomly-named temporary file in the default temp directory. @param suffix a file suffix to use for the file @@ -94,8 +104,24 @@ public: The file will not be created until you write to it. And remember that when this object is deleted, the file will also be deleted! */ - TemporaryFile (const String& suffix = String(), - int optionFlags = 0); + TemporaryFile (const String& suffix, + int optionFlags); + + /** Creates a temporary file in the same directory as a specified file. + + This is useful if you have a file that you want to overwrite, but don't + want to harm the original file if the write operation fails. You can + use this to create a temporary file next to the target file, then + write to the temporary file, and finally use overwriteTargetFileWithTemporary() + to replace the target file with the one you've just written. + + This class won't create any files until you actually write to them. And remember + that when this object is deleted, the temporary file will also be deleted! + + @param targetFile the file that you intend to overwrite - the temporary + file will be created in the same directory as this + */ + explicit TemporaryFile (const File& targetFile); /** Creates a temporary file in the same directory as a specified file. @@ -113,7 +139,7 @@ public: @param optionFlags a combination of the values listed in the OptionFlags enum */ TemporaryFile (const File& targetFile, - int optionFlags = 0); + int optionFlags); /** Creates a temporary file using an explicit filename. The other constructors are a better choice than this one, unless for some reason From c12ab11ee47327aecb9ce55f340c8b5101b03753 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 22 Nov 2024 10:27:34 +0000 Subject: [PATCH 038/557] MIDI CI: Remove unnecessary call to setSeedRandomly --- modules/juce_midi_ci/ci/juce_CIDevice.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/juce_midi_ci/ci/juce_CIDevice.cpp b/modules/juce_midi_ci/ci/juce_CIDevice.cpp index 6c2d55a642..ae43eabc6a 100644 --- a/modules/juce_midi_ci/ci/juce_CIDevice.cpp +++ b/modules/juce_midi_ci/ci/juce_CIDevice.cpp @@ -1168,7 +1168,6 @@ private: static MUID getReallyRandomMuid() { Random random; - random.setSeedRandomly(); return MUID::makeRandom (random); } From 7ab382d357fe92f347792fa51d0f1a2e1381ea89 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Thu, 28 Nov 2024 11:09:03 +0000 Subject: [PATCH 039/557] VST3 Client: Remove unhelpful jassert --- .../juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp index 63045e3dbd..2e62ed015c 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp @@ -1673,9 +1673,6 @@ private: #endif SharedBase{}); - if (targetIID == Vst::IRemapParamID::iid) - jassertfalse; - if (result.isOk()) return result; From c2f567f3ee39060e86c4dfc94199aae8601909e9 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 2 Dec 2024 14:14:27 +0000 Subject: [PATCH 040/557] Fix unused variable warnings --- .../utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp | 2 +- .../juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm | 2 +- .../juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm | 4 ++-- modules/juce_video/native/juce_CameraDevice_ios.h | 4 ++-- modules/juce_video/native/juce_CameraDevice_mac.h | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp b/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp index 588eb554e1..c34061e153 100644 --- a/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp +++ b/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp @@ -134,7 +134,7 @@ void AudioProcessorARAExtension::didBindToARA() noexcept playbackRenderer->araExtension = this; #endif -#if (! JUCE_DISABLE_ASSERTIONS) +#if JUCE_ASSERTIONS_ENABLED_OR_LOGGED // validate proper subclassing of the instance role classes if (auto playbackRenderer = getPlaybackRenderer()) jassert (dynamic_cast (playbackRenderer) != nullptr); diff --git a/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm b/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm index 664243a03a..0f5ff074f3 100644 --- a/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm +++ b/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm @@ -1633,7 +1633,7 @@ public: const auto handled = [&]() -> bool { - if (auto* target = findCurrentTextInputTarget()) + if (findCurrentTextInputTarget() != nullptr) if (const auto* inputContext = [view inputContext]) return [inputContext handleEvent: ev] && ! viewCannotHandleEvent; diff --git a/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm b/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm index 2d7835d82b..c3f6341ba6 100644 --- a/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm +++ b/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm @@ -1107,7 +1107,7 @@ static void postTraitChangeNotification (UITraitCollection* previousTraitCollect - (BOOL) canPerformAction: (SEL) action withSender: (id) sender { - if (auto* target = [self getTextInputTarget]) + if ([self getTextInputTarget] != nullptr) { if (action == @selector (paste:)) return [[UIPasteboard generalPasteboard] hasStrings]; @@ -1266,7 +1266,7 @@ static void postTraitChangeNotification (UITraitCollection* previousTraitCollect - (UITextRange*) markedTextRange { if (owner != nullptr && owner->stringBeingComposed.isNotEmpty()) - if (auto* target = owner->findCurrentTextInputTarget()) + if (owner->findCurrentTextInputTarget() != nullptr) return [JuceUITextRange withRange: owner->getMarkedTextRange()]; return nil; diff --git a/modules/juce_video/native/juce_CameraDevice_ios.h b/modules/juce_video/native/juce_CameraDevice_ios.h index 395385fa70..d0812c71d8 100644 --- a/modules/juce_video/native/juce_CameraDevice_ios.h +++ b/modules/juce_video/native/juce_CameraDevice_ios.h @@ -646,7 +646,7 @@ private: printImageOutputDebugInfo (captureOutput); - if (auto* connection = findVideoConnection (captureOutput)) + if (findVideoConnection (captureOutput) != nullptr) { auto* photoOutput = (AVCapturePhotoOutput*) captureOutput; auto outputConnection = [photoOutput connectionWithMediaType: AVMediaTypeVideo]; @@ -1176,7 +1176,7 @@ struct CameraDevice::ViewerComponent : public UIViewComponent { if ([keyPath isEqualToString: @"videoRotationAngleForHorizonLevelPreview"]) { - if (auto* previewLayer = getPreviewLayer (self)) + if (getPreviewLayer (self) != nullptr) { auto* viewer = static_cast (context); auto& session = viewer->cameraDevice.pimpl->captureSession; diff --git a/modules/juce_video/native/juce_CameraDevice_mac.h b/modules/juce_video/native/juce_CameraDevice_mac.h index df409217db..f87dcd6893 100644 --- a/modules/juce_video/native/juce_CameraDevice_mac.h +++ b/modules/juce_video/native/juce_CameraDevice_mac.h @@ -542,7 +542,7 @@ private: startSession(); - if (auto* videoConnection = getVideoConnection()) + if (getVideoConnection() != nullptr) imageOutput->triggerImageCapture (*this); } From 2583b0648132e6ff3df839e7f2caf4388d31080d Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 2 Dec 2024 15:20:49 +0000 Subject: [PATCH 041/557] NSViewComponentPeer: Guard API availability --- .../native/juce_NSViewComponentPeer_mac.mm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm b/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm index 0f5ff074f3..58c33abafa 100644 --- a/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm +++ b/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm @@ -129,14 +129,16 @@ static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept constexpr int extendedKeyModifier = 0x30000; //============================================================================== -class JuceCALayerDelegate final : public ObjCClass> +struct JuceCALayerDelegateCallback +{ + virtual ~JuceCALayerDelegateCallback() = default; + virtual void displayLayer (CALayer*) = 0; +}; + +class API_AVAILABLE (macos (10.12)) JuceCALayerDelegate final : public ObjCClass> { public: - struct Callback - { - virtual ~Callback() = default; - virtual void displayLayer (CALayer*) = 0; - }; + using Callback = JuceCALayerDelegateCallback; static NSObject* construct (Callback* owner) { @@ -176,7 +178,7 @@ private: //============================================================================== class NSViewComponentPeer final : public ComponentPeer, - private JuceCALayerDelegate::Callback + private JuceCALayerDelegateCallback { public: NSViewComponentPeer (Component& comp, const int windowStyleFlags, NSView* viewToAttachTo) From dfe4858e555656b2a8a2735e5967c8cb4cbe731c Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 25 Nov 2024 15:33:04 +0000 Subject: [PATCH 042/557] AudioTransportSource: Avoid nullptr dereference in hasStreamFinished() --- .../juce_audio_devices/sources/juce_AudioTransportSource.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp b/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp index 69cbd1b30c..8bf92ba0aa 100644 --- a/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp +++ b/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp @@ -168,8 +168,9 @@ double AudioTransportSource::getLengthInSeconds() const bool AudioTransportSource::hasStreamFinished() const noexcept { - return positionableSource->getNextReadPosition() > positionableSource->getTotalLength() + 1 - && ! positionableSource->isLooping(); + return positionableSource == nullptr + || (positionableSource->getNextReadPosition() > positionableSource->getTotalLength() + 1 + && ! positionableSource->isLooping()); } void AudioTransportSource::setNextReadPosition (int64 newPosition) From 3186522b0bd8801b6fe50f917e96dca3f9d500bf Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 25 Nov 2024 16:45:01 +0000 Subject: [PATCH 043/557] VST3 Host: Fix bug where MIDI CCs mapped to parameters would fail to update the host and editcontroller --- .../format_types/juce_VST3Common.h | 2 +- .../format_types/juce_VST3PluginFormat.cpp | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index 8b1171ebbb..6b93925de9 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -1430,7 +1430,7 @@ private: controlEvent->controllerNumber); if (controlParamID != Steinberg::Vst::kNoParamId) - callback (controlParamID, controlEvent->paramValue, msg.getTimeStamp()); + callback (controlParamID, (float) controlEvent->paramValue, msg.getTimeStamp()); return true; } diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 41cddcc03c..fb122ff5d0 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -2593,6 +2593,11 @@ public: return cachedInfo; } + Steinberg::int32 getVstParamIndex() const + { + return vstParamIndex; + } + private: Vst::ParameterInfo fetchParameterInfo() const { @@ -3770,12 +3775,21 @@ private: if (acceptsMidi()) { - const auto midiMessageCallback = [&] (auto controlID, auto paramValue, auto time) + const auto midiMessageCallback = [&] (auto controlID, float paramValue, auto time) { Steinberg::int32 queueIndex{}; if (auto* queue = inputParameterChanges->addParameterData (controlID, queueIndex)) - queue->append ({ (Steinberg::int32) time, (float) paramValue }); + queue->append ({ (Steinberg::int32) time, paramValue }); + + if (auto* param = getParameterForID (controlID)) + { + // Send the parameter value to the editor + parameterDispatcher.push (param->getVstParamIndex(), paramValue); + + // Update the host's view of the parameter value + param->setValueWithoutUpdatingProcessor (paramValue); + } }; MidiEventList::hostToPluginEventList (*midiInputs, From 89a2407debe74ea5317010d64de3188cb084b093 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 25 Nov 2024 17:06:56 +0000 Subject: [PATCH 044/557] AudioFormatReader: Update searchForLevel to work for more than two channels --- .../format/juce_AudioFormatReader.cpp | 53 +++++++------------ 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/modules/juce_audio_formats/format/juce_AudioFormatReader.cpp b/modules/juce_audio_formats/format/juce_AudioFormatReader.cpp index 57efbccf7d..5d61027bc5 100644 --- a/modules/juce_audio_formats/format/juce_AudioFormatReader.cpp +++ b/modules/juce_audio_formats/format/juce_AudioFormatReader.cpp @@ -299,11 +299,12 @@ int64 AudioFormatReader::searchForLevel (int64 startSample, return -1; const int bufferSize = 4096; - HeapBlock tempSpace (bufferSize * 2 + 64); + const size_t channels = numChannels; + HeapBlock tempSpace (bufferSize * channels + 64); + std::vector channelPointers (channels); - int* tempBuffer[3] = { tempSpace.get(), - tempSpace.get() + bufferSize, - nullptr }; + for (auto [index, ptr] : enumerate (channelPointers, size_t{})) + ptr = tempSpace + (bufferSize * index); int consecutive = 0; int64 firstMatchPos = -1; @@ -326,7 +327,7 @@ int64 AudioFormatReader::searchForLevel (int64 startSample, if (bufferStart >= lengthInSamples) break; - read (tempBuffer, 2, bufferStart, numThisTime, false); + read (channelPointers.data(), (int) channels, bufferStart, numThisTime, false); auto num = numThisTime; while (--num >= 0) @@ -334,43 +335,25 @@ int64 AudioFormatReader::searchForLevel (int64 startSample, if (numSamplesToSearch < 0) --startSample; - bool matches = false; auto index = (int) (startSample - bufferStart); - if (usesFloatingPointData) + const auto matches = std::invoke ([&] { - const float sample1 = std::abs (((float*) tempBuffer[0]) [index]); - - if (sample1 >= magnitudeRangeMinimum - && sample1 <= magnitudeRangeMaximum) + if (usesFloatingPointData) { - matches = true; + return std::any_of (channelPointers.begin(), channelPointers.end(), [&] (const auto& ptr) + { + const float sample = std::abs (((float*) ptr) [index]); + return magnitudeRangeMinimum <= sample && sample <= magnitudeRangeMaximum; + }); } - else if (numChannels > 1) - { - const float sample2 = std::abs (((float*) tempBuffer[1]) [index]); - matches = (sample2 >= magnitudeRangeMinimum - && sample2 <= magnitudeRangeMaximum); - } - } - else - { - const int sample1 = std::abs (tempBuffer[0] [index]); - - if (sample1 >= intMagnitudeRangeMinimum - && sample1 <= intMagnitudeRangeMaximum) + return std::any_of (channelPointers.begin(), channelPointers.end(), [&] (const auto& ptr) { - matches = true; - } - else if (numChannels > 1) - { - const int sample2 = std::abs (tempBuffer[1][index]); - - matches = (sample2 >= intMagnitudeRangeMinimum - && sample2 <= intMagnitudeRangeMaximum); - } - } + const int sample = std::abs (ptr[index]); + return intMagnitudeRangeMinimum <= sample && sample <= intMagnitudeRangeMaximum; + }); + }); if (matches) { From 6f85c2c8622aecfd831c02a92c779c4898317c3d Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 22 Nov 2024 21:42:51 +0100 Subject: [PATCH 045/557] CMake: Add missing modules to package script --- extras/Build/CMake/JUCEConfig.cmake.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extras/Build/CMake/JUCEConfig.cmake.in b/extras/Build/CMake/JUCEConfig.cmake.in index c0c1eef2dd..03a09efb4a 100644 --- a/extras/Build/CMake/JUCEConfig.cmake.in +++ b/extras/Build/CMake/JUCEConfig.cmake.in @@ -39,6 +39,7 @@ include("@PACKAGE_UTILS_INSTALL_DIR@/JUCEUtils.cmake") set(_juce_modules juce_analytics + juce_animation juce_audio_basics juce_audio_devices juce_audio_formats @@ -55,6 +56,7 @@ set(_juce_modules juce_gui_basics juce_gui_extra juce_javascript + juce_midi_ci juce_opengl juce_osc juce_product_unlocking From 3c75e7eeeb3b4d3304544f8954cc0a28427c81a9 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 25 Nov 2024 19:16:34 +0000 Subject: [PATCH 046/557] GuiApp: Remove redundant qualifications from identifiers --- examples/CMake/GuiApp/Main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/CMake/GuiApp/Main.cpp b/examples/CMake/GuiApp/Main.cpp index 6930f3e0db..702aaa4dc6 100644 --- a/examples/CMake/GuiApp/Main.cpp +++ b/examples/CMake/GuiApp/Main.cpp @@ -57,8 +57,8 @@ public: explicit MainWindow (juce::String name) : DocumentWindow (name, juce::Desktop::getInstance().getDefaultLookAndFeel() - .findColour (ResizableWindow::backgroundColourId), - DocumentWindow::allButtons) + .findColour (backgroundColourId), + allButtons) { setUsingNativeTitleBar (true); setContentOwned (new MainComponent(), true); @@ -78,7 +78,7 @@ public: // This is called when the user tries to close this window. Here, we'll just // ask the app to quit when this happens, but you can change this to do // whatever you need. - JUCEApplication::getInstance()->systemRequestedQuit(); + getInstance()->systemRequestedQuit(); } /* Note: Be careful if you override any DocumentWindow methods - the base From 4b9253dc762c841cb2f64f7f167e498700ec7f6f Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 27 Nov 2024 16:54:41 +0000 Subject: [PATCH 047/557] DirectX: Make it easier to enable debug logging output --- modules/juce_graphics/native/juce_DirectX_windows.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/juce_graphics/native/juce_DirectX_windows.h b/modules/juce_graphics/native/juce_DirectX_windows.h index 2e911b9c59..516d20248e 100644 --- a/modules/juce_graphics/native/juce_DirectX_windows.h +++ b/modules/juce_graphics/native/juce_DirectX_windows.h @@ -35,6 +35,8 @@ namespace juce { +constexpr auto enableDirectXDebugLayer = false; + struct DxgiAdapter : public ReferenceCountedObject { using Ptr = ReferenceCountedObjectPtr; @@ -60,7 +62,8 @@ struct DxgiAdapter : public ReferenceCountedObject // This flag adds support for surfaces with a different color channel ordering // than the API default. It is required for compatibility with Direct2D. - const auto creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + const auto creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT + | (enableDirectXDebugLayer ? D3D11_CREATE_DEVICE_DEBUG : 0); if (const auto hr = D3D11CreateDevice (result->dxgiAdapter, D3D_DRIVER_TYPE_UNKNOWN, @@ -266,7 +269,7 @@ private: ComSmartPtr d2dSharedFactory = [&] { D2D1_FACTORY_OPTIONS options; - options.debugLevel = D2D1_DEBUG_LEVEL_NONE; + options.debugLevel = enableDirectXDebugLayer ? D2D1_DEBUG_LEVEL_INFORMATION : D2D1_DEBUG_LEVEL_NONE; JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") ComSmartPtr result; auto hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_MULTI_THREADED, From 543ae06632a571920da44ddfe6c18bdcd1d9ea6b Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 25 Nov 2024 18:29:35 +0000 Subject: [PATCH 048/557] Windowing: Fix issue where edge resizers could incorrectly be displayed for non-resizable windows The buggy behaviour could be seen in a blank GUI app project by setting a native titlebar and calling setResizable (false, false). The resulting window would still display a resize cursor when hovering the window border. --- modules/juce_gui_basics/native/juce_Windowing_windows.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index bf82c01d32..4dc75bc04f 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -2390,6 +2390,7 @@ private: const auto hasMax = (styleFlags & windowHasMaximiseButton) != 0; const auto appearsOnTaskbar = (styleFlags & windowAppearsOnTaskbar) != 0; const auto resizable = (styleFlags & windowIsResizable) != 0; + const auto usesDropShadow = windowUsesNativeShadow(); if (parentToAddTo != nullptr) { @@ -2397,13 +2398,14 @@ private: } else { - if (titled || windowUsesNativeShadow()) + if (titled || usesDropShadow) { + type |= usesDropShadow ? WS_CAPTION : 0; type |= titled ? (WS_OVERLAPPED | WS_CAPTION) : WS_POPUP; type |= hasClose ? (WS_SYSMENU | WS_CAPTION) : 0; type |= hasMin ? (WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU) : 0; type |= hasMax ? (WS_MAXIMIZEBOX | WS_CAPTION | WS_SYSMENU) : 0; - type |= resizable || windowUsesNativeShadow() ? WS_THICKFRAME : 0; + type |= resizable ? WS_THICKFRAME : 0; } else { @@ -2420,7 +2422,7 @@ private: L"", type, 0, 0, 0, 0, parentToAddTo, nullptr, (HINSTANCE) Process::getCurrentModuleInstanceHandle(), nullptr); - if (! titled && windowUsesNativeShadow()) + if (! titled && usesDropShadow) { // The choice of margins is very particular. // - Using 0 for all values disables the system decoration (shadow etc.) completely. From 90d9f573c299519735400849614e9c8c2b0ee163 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 25 Nov 2024 21:12:44 +0000 Subject: [PATCH 049/557] Windowing: Avoid using bogus window-border sizes when computing constrained window sizes Previously, for windows with a native titlebar and a constrainer, the window could be restored at the wrong size. This happened because findPhysicalBorderSize() may return nonsensical values when called during a SC_RESTORE, which in turn produces an unexpected window size when adding the bogus border size to the constrained client area size. We now avoid trying to constrain the window if we're unable to determine the correct border size. I think this is only likely to happen during SC_RESTORE, in which case the system should have a pretty good idea of where the window should go, and constraining should not be necessary. --- .../native/juce_Windowing_windows.cpp | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index 4dc75bc04f..d092112be3 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -1530,7 +1530,7 @@ public: return BorderSize { 0, 0, 0, 0 }; } - BorderSize findPhysicalBorderSize() const + std::optional> findPhysicalBorderSize() const { if (const auto custom = getCustomBorderSize()) return *custom; @@ -1543,10 +1543,15 @@ public: if (! GetWindowInfo (hwnd, &info)) return {}; - return { info.rcClient.top - info.rcWindow.top, - info.rcClient.left - info.rcWindow.left, - info.rcWindow.bottom - info.rcClient.bottom, - info.rcWindow.right - info.rcClient.right }; + // Sometimes GetWindowInfo returns bogus information when called in the middle of restoring + // the window + if (info.rcWindow.left <= -32000 && info.rcWindow.top <= -32000) + return {}; + + return BorderSize { info.rcClient.top - info.rcWindow.top, + info.rcClient.left - info.rcWindow.left, + info.rcWindow.bottom - info.rcClient.bottom, + info.rcWindow.right - info.rcClient.right }; } void setBounds (const Rectangle& bounds, bool isNowFullScreen) override @@ -1563,7 +1568,7 @@ public: const ScopedValueSetter scope (shouldIgnoreModalDismiss, true); - const auto borderSize = findPhysicalBorderSize(); + const auto borderSize = findPhysicalBorderSize().value_or (BorderSize{}); auto newBounds = borderSize.addedTo ([&] { ScopedThreadDPIAwarenessSetter setter { hwnd }; @@ -1630,7 +1635,7 @@ public: // This means that we may end up clipping off up to one logical pixel under the physical // window border, but this is preferable to displaying an uninitialised/unpainted // region of the client area. - const auto physicalBorder = findPhysicalBorderSize(); + const auto physicalBorder = findPhysicalBorderSize().value_or (BorderSize{}); const auto physicalBounds = D2DUtilities::toRectangle (getWindowScreenRect (hwnd)); const auto physicalClient = physicalBorder.subtractedFrom (physicalBounds); @@ -1781,7 +1786,7 @@ public: BorderSize getFrameSize() const override { - return findPhysicalBorderSize().multipliedBy (1.0 / scaleFactor); + return findPhysicalBorderSize().value_or (BorderSize{}).multipliedBy (1.0 / scaleFactor); } bool setAlwaysOnTop (bool alwaysOnTop) override @@ -3363,7 +3368,11 @@ private: movingLeft, movingBottom, movingRight); - r = D2DUtilities::toRECT (modifiedPhysicalBounds); + + if (! modifiedPhysicalBounds.has_value()) + return TRUE; + + r = D2DUtilities::toRECT (*modifiedPhysicalBounds); } return TRUE; @@ -3378,12 +3387,14 @@ private: && ! Component::isMouseButtonDownAnywhere()) { const auto requestedPhysicalBounds = D2DUtilities::toRectangle ({ wp.x, wp.y, wp.x + wp.cx, wp.y + wp.cy }); - const auto modifiedPhysicalBounds = getConstrainedBounds (requestedPhysicalBounds, false, false, false, false); - wp.x = modifiedPhysicalBounds.getX(); - wp.y = modifiedPhysicalBounds.getY(); - wp.cx = modifiedPhysicalBounds.getWidth(); - wp.cy = modifiedPhysicalBounds.getHeight(); + if (const auto modifiedPhysicalBounds = getConstrainedBounds (requestedPhysicalBounds, false, false, false, false)) + { + wp.x = modifiedPhysicalBounds->getX(); + wp.y = modifiedPhysicalBounds->getY(); + wp.cx = modifiedPhysicalBounds->getWidth(); + wp.cy = modifiedPhysicalBounds->getHeight(); + } } } @@ -3395,9 +3406,17 @@ private: return 0; } - Rectangle getConstrainedBounds (Rectangle proposed, bool top, bool left, bool bottom, bool right) const + std::optional> getConstrainedBounds (Rectangle proposed, + bool top, + bool left, + bool bottom, + bool right) const { const auto physicalBorder = findPhysicalBorderSize(); + + if (! physicalBorder.has_value()) + return {}; + const auto logicalBorder = getFrameSize(); // The constrainer expects to operate in logical coordinate space. @@ -3409,7 +3428,7 @@ private: // After the constrainer returns, we substitute in the other direction, replacing logical // borders with physical. const auto requestedPhysicalBounds = proposed; - const auto requestedPhysicalClient = physicalBorder.subtractedFrom (requestedPhysicalBounds); + const auto requestedPhysicalClient = physicalBorder->subtractedFrom (requestedPhysicalBounds); const auto requestedLogicalClient = detail::ScalingHelpers::unscaledScreenPosToScaled ( component, convertPhysicalScreenRectangleToLogical (requestedPhysicalClient, hwnd)); @@ -3455,7 +3474,7 @@ private: return modified; }(); - return physicalBorder.addedTo (withSnappedPosition); + return physicalBorder->addedTo (withSnappedPosition); } enum class ForceRefreshDispatcher From 81a95abb3c8049fe5208f3f7c309f015aebfc7a2 Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 26 Nov 2024 13:20:29 +0000 Subject: [PATCH 050/557] Windowing: Use local coordinate space of component in call to findControlAtPoint --- .../native/juce_Windowing_windows.cpp | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index d092112be3..8cb548ee43 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -2032,7 +2032,7 @@ public: } HWNDComponentPeer& peer; - ComponentPeer::DragInfo dragInfo; + DragInfo dragInfo; bool peerIsDeleted = false; private: @@ -3740,12 +3740,12 @@ private: static ModifierKeys getMouseModifiers() { - HWNDComponentPeer::updateKeyModifiers(); + updateKeyModifiers(); int mouseMods = 0; - if (HWNDComponentPeer::isKeyDown (VK_LBUTTON)) mouseMods |= ModifierKeys::leftButtonModifier; - if (HWNDComponentPeer::isKeyDown (VK_RBUTTON)) mouseMods |= ModifierKeys::rightButtonModifier; - if (HWNDComponentPeer::isKeyDown (VK_MBUTTON)) mouseMods |= ModifierKeys::middleButtonModifier; + if (isKeyDown (VK_LBUTTON)) mouseMods |= ModifierKeys::leftButtonModifier; + if (isKeyDown (VK_RBUTTON)) mouseMods |= ModifierKeys::rightButtonModifier; + if (isKeyDown (VK_MBUTTON)) mouseMods |= ModifierKeys::middleButtonModifier; ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (mouseMods); @@ -3796,8 +3796,12 @@ private: if (const auto result = DefWindowProc (h, message, wParam, lParam); HTSIZEFIRST <= result && result <= HTSIZELAST) return result; - const auto localPoint = getLocalPointFromScreenLParam (lParam).toFloat(); - const auto kind = component.findControlAtPoint (localPoint); + const auto physicalPoint = D2DUtilities::toPoint (getPOINTFromLParam (lParam)); + const auto logicalPoint = convertPhysicalScreenPointToLogical (physicalPoint, hwnd); + const auto localPoint = globalToLocal (logicalPoint.toFloat()); + const auto componentPoint = detail::ScalingHelpers::unscaledScreenPosToScaled (component, localPoint); + + const auto kind = component.findControlAtPoint (componentPoint); using Kind = Component::WindowControlKind; switch (kind) @@ -4093,7 +4097,7 @@ private: if (auto* modal = Component::getCurrentlyModalComponent()) if (auto* peer = modal->getPeer()) - if ((peer->getStyleFlags() & ComponentPeer::windowIsTemporary) != 0) + if ((peer->getStyleFlags() & windowIsTemporary) != 0) sendInputAttemptWhenModalMessage(); break; From c24d1a17a72be472d559d19cfb399ffbaf40601b Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 27 Nov 2024 16:55:49 +0000 Subject: [PATCH 051/557] Windowing: Avoid changing window bounds in WM_WINDOWPOSCHANGING This change intends to address a bug observed only on Windows 10 with a display scale factor of 125%. When the native titlebar is enabled, and the window's border-resizer is used to resize the window slowly the with mouse, the client area of the window may move to the wrong location, or be drawn with some areas obscured/clipped. This is especially observable when resizing the WidgetsDemo to its smallest size, and then dragging the right border a single pixel to the right. On my computer, this consistently causes the client area to display at the wrong location. I haven't been able to find any obvious bug in JUCE that might cause this behaviour. In particular, it seems that the window begins displaying incorrectly *before* the window ever actually resizes. During the resize, the system sends events (WM_SIZING and WM_WINDOWPOSCHANGING) to the window, and according to the documentation, the window may modify the message parameters in order to constrain the new window size. When running on a scaled display, JUCE attempts to map the logical client area size to a sensible size in physical pixels, and uses the sizing messages to enforce this size requirement. In the case of the broken window rendering, the system requests a new window size, which JUCE rejects. The window's display state doesn't change, so the swap chain does not resize, and the swap chain does not present. Put another way, the broken rendering happens *independently* of JUCE modifying the swap chain in any way. Therefore, I believe that the bug is introduced elsewhere, potentially by Windows itself. I also checked to see whether the issue could be caused by mishandling of the NCCALCSIZE message, which is normally used to configure the relative positions of the client and nonclient areas. However, in the buggy case, NCCALCSIZE is not sent until *after* the first 'broken' frame is painted - and even then, the implementation immediately falls back to DefWindowProc. Given that the issue appears to be a bug in Windows, the proposed change is a workaround, rather than a true fix. It appears as though the problem goes away when WM_WINDOWPOSCHANGING does not modify the requested bounds. Therefore, for windows with native titlebars, we rely on the constraints to be applied in WM_SIZING only, when sizing the window in a sizemove gesture. --- .../native/juce_Windowing_windows.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index 8cb548ee43..8af733c511 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -2231,7 +2231,7 @@ private: uint32 lastPaintTime = 0; ULONGLONG lastMagnifySize = 0; bool isDragging = false, isMouseOver = false, - hasCreatedCaret = false, constrainerIsResizing = false; + hasCreatedCaret = false, constrainerIsResizing = false, sizing = false; IconConverters::IconPtr currentWindowIcon; FileDropTarget* dropTarget = nullptr; UWPUIViewSettings uwpViewSettings; @@ -4010,13 +4010,25 @@ private: break; //============================================================================== + case WM_ENTERSIZEMOVE: + sizing = true; + break; + + case WM_EXITSIZEMOVE: + sizing = false; + break; + case WM_SIZING: + sizing = true; return handleSizeConstraining (*(RECT*) lParam, wParam); case WM_MOVING: return handleSizeConstraining (*(RECT*) lParam, 0); case WM_WINDOWPOSCHANGING: + if (hasTitleBar() && sizing) + break; + return handlePositionChanging (*(WINDOWPOS*) lParam); case 0x2e0: /* WM_DPICHANGED */ return handleDPIChanging ((int) HIWORD (wParam), *(RECT*) lParam); From 2fcf9ebf38982c9487b334367a8b7c05c4ccb330 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 27 Nov 2024 20:10:35 +0000 Subject: [PATCH 052/557] Windowing: Fix issue where components dragged between monitors with different scalings could detach from the mouse This bug could be observed by running the WidgetsDemo Drag+Drop pane on Windows 10, and dragging an item between two displays at different scale factors. This is issue is a regression introduced in 9817a2bb66408ba44cdf365f90f91fd33afedcc9. The regression was caused by the change in mouse position calculation. The incorrect version switched to using ClientToScreen, but the correct version used getPointFromLocalLParam. The function getPointFromLocalLParam was replaced by clientLParamToPoint in 24ab3cb6a3aa926b963614669a0ac9ab609333cf, and is restored by this commit. --- .../native/juce_Windowing_windows.cpp | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index 8af733c511..6ad1d16dd1 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -2683,22 +2683,15 @@ private: client, }; - std::optional doMouseMove (LPARAM lParam, bool isMouseDownEvent, WindowArea area) + std::optional doMouseMove (const LPARAM lParam, bool isMouseDownEvent, WindowArea area) { - auto point = getPOINTFromLParam (lParam); - - if (area == WindowArea::client) - ClientToScreen (hwnd, &point); - - const auto adjustedLParam = MAKELPARAM (point.x, point.y); - // Check if the mouse has moved since being pressed in the caption area. // If it has, then we defer to DefWindowProc to handle the mouse movement. // Allowing DefWindowProc to handle WM_NCLBUTTONDOWN directly will pause message // processing (and therefore painting) when the mouse is clicked in the caption area, // which is why we wait until the mouse is *moved* before asking the system to take over. // Letting the system handle the move is important for things like Aero Snap to work. - if (captionMouseDown.has_value() && *captionMouseDown != adjustedLParam) + if (area == WindowArea::nonclient && captionMouseDown.has_value() && *captionMouseDown != lParam) { captionMouseDown.reset(); @@ -2710,20 +2703,21 @@ private: // ModifierKeys::currentModifiers gets left in the wrong state. As a workaround, we // manually update the modifier keys after DefWindowProc exits, and update the // capture state if necessary. - const auto result = DefWindowProc (hwnd, WM_NCLBUTTONDOWN, HTCAPTION, adjustedLParam); + const auto result = DefWindowProc (hwnd, WM_NCLBUTTONDOWN, HTCAPTION, lParam); getMouseModifiers(); releaseCaptureIfNecessary(); return result; } - const auto position = getLocalPointFromScreenLParam (adjustedLParam); - ModifierKeys modsToSend (ModifierKeys::currentModifiers); // this will be handled by WM_TOUCH if (isTouchEvent() || areOtherTouchSourcesActive()) return {}; + const auto position = area == WindowArea::client ? getPointFromLocalLParam (lParam) + : getLocalPointFromScreenLParam (lParam); + if (! isMouseOver) { isMouseOver = true; @@ -2808,7 +2802,7 @@ private: isDragging = true; - doMouseEvent (clientLparamToPoint (lParam), MouseInputSource::defaultPressure); + doMouseEvent (getPointFromLocalLParam (lParam), MouseInputSource::defaultPressure); } } @@ -3726,11 +3720,21 @@ private: return globalToLocal (convertPhysicalScreenPointToLogical (globalPos, hwnd).toFloat()); } - Point clientLparamToPoint (LPARAM lParam) + Point getPointFromLocalLParam (LPARAM lParam) noexcept { - auto point = getPOINTFromLParam (lParam); - ClientToScreen (hwnd, &point); - return getLocalPointFromScreenLParam (MAKELPARAM (point.x, point.y)); + const auto p = D2DUtilities::toPoint (getPOINTFromLParam (lParam)); + + if (! isPerMonitorDPIAwareWindow (hwnd)) + return p.toFloat(); + + // LPARAM is relative to this window's top-left but may be on a different monitor so we need to calculate the + // physical screen position and then convert this to local logical coordinates + auto r = getWindowScreenRect (hwnd); + const auto windowBorder = findPhysicalBorderSize().value_or (BorderSize{}); + const auto offset = p + + Point { (int) r.left, (int) r.top } + + Point { windowBorder.getLeft(), windowBorder.getTop() }; + return globalToLocal (Desktop::getInstance().getDisplays().physicalToLogical (offset).toFloat()); } Point getCurrentMousePos() noexcept @@ -3978,7 +3982,7 @@ private: case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: - doMouseUp (clientLparamToPoint (lParam), wParam); + doMouseUp (getPointFromLocalLParam (lParam), wParam); return 0; case WM_POINTERWHEEL: From fc76e936d3bcd79642b9244abd2b5f45698c1124 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 28 Nov 2024 16:19:12 +0000 Subject: [PATCH 053/557] DragImageComponent: Avoid case where image may detach from mouse when dragging between screens When two monitors are available, both with different scale factors, then the drag-image may 'detach' from the mouse while the image's top-left coordinate was on one display, and the mouse cursor was on the other display. This happened because, on Windows, the mouse cursor moves continuously in physical (not logical!) space. In other words, the mouse may not move continuously in logical space, and the discontinuity becomes visible when components are positioned relative to the mouse in logical space. In order to display consistently, the top-left position of the image must be set relative to the physical position of the mouse. --- .../mouse/juce_DragAndDropContainer.cpp | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp index 88fe008996..3ec2a0b18d 100644 --- a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp +++ b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp @@ -324,12 +324,35 @@ private: void setNewScreenPos (Point screenPos) { - auto newPos = screenPos - imageOffset; + setTopLeftPosition (std::invoke ([&] + { + if (auto* p = getParentComponent()) + return p->getLocalPoint (nullptr, screenPos - imageOffset); - if (auto* p = getParentComponent()) - newPos = p->getLocalPoint (nullptr, newPos); + #if JUCE_WINDOWS + // On Windows, the mouse position is continuous in physical pixels across screen boundaries. + // i.e. if two screens are set to different scale factors, when the mouse moves horizontally + // between those screens, the mouse's physical y coordinate will be preserved, and if + // the mouse moves vertically between screens its physical x coordinate will be preserved. - setTopLeftPosition (newPos); + // To avoid the dragged image detaching from the mouse, compute the new top left position + // in physical coords and then convert back to logical. + // If we were to stay in logical coordinates the whole time, the image may detach from the + // mouse because the mouse does not move continuously in logical coordinate space. + + const auto& displays = Desktop::getInstance().getDisplays(); + const auto physicalPos = displays.logicalToPhysical (screenPos); + + float scale = 1.0f; + + if (auto* p = getPeer()) + scale = (float) p->getPlatformScaleFactor(); + + return displays.physicalToLogical (physicalPos - (imageOffset * scale)); + #else + return screenPos - imageOffset; + #endif + })); } void sendDragMove (DragAndDropTarget::SourceDetails& details) const From 038b0d6c9ee652437cae3cbc0aa08f295ca30b1b Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 28 Nov 2024 20:42:46 +0000 Subject: [PATCH 054/557] Windows: Fix bug where IME failed to display on Windows 11 --- modules/juce_gui_basics/native/juce_Windowing_windows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index 6ad1d16dd1..cafc6a727b 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -4328,7 +4328,7 @@ private: case WM_IME_SETCONTEXT: imeHandler.handleSetContext (h, wParam == TRUE); lParam &= ~(LPARAM) ISC_SHOWUICOMPOSITIONWINDOW; - return ImmIsUIMessage (h, message, wParam, lParam); + break; case WM_IME_STARTCOMPOSITION: imeHandler.handleStartComposition (*this); return 0; case WM_IME_ENDCOMPOSITION: imeHandler.handleEndComposition (*this, h); return 0; From cfee7cfc9388c69b881c96f1f01abf13dc35ec72 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 28 Nov 2024 20:43:04 +0000 Subject: [PATCH 055/557] Windows: Fix bug where IME displayed at incorrect location on scaled displays --- modules/juce_gui_basics/native/juce_Windowing_windows.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index cafc6a727b..bf8d63c3fa 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -4586,9 +4586,10 @@ private: { if (auto* targetComp = dynamic_cast (target)) { - auto area = peer.getComponent().getLocalArea (targetComp, target->getCaretRectangle()); + const auto screenPos = targetComp->localPointToGlobal (target->getCaretRectangle().getBottomLeft()); + const auto relativePos = peer.globalToLocal (screenPos) * peer.getPlatformScaleFactor(); - CANDIDATEFORM pos = { 0, CFS_CANDIDATEPOS, { area.getX(), area.getBottom() }, { 0, 0, 0, 0 } }; + CANDIDATEFORM pos { 0, CFS_CANDIDATEPOS, D2DUtilities::toPOINT (relativePos), { 0, 0, 0, 0 } }; ImmSetCandidateWindow (hImc, &pos); } } From 0e12c2da92f3d32d7157aa0a8e8bcb22197805dd Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Mon, 2 Dec 2024 15:24:16 +0000 Subject: [PATCH 056/557] VST3 Client: Fix an issue with the reporting of VST3 plugin IDs --- .../utilities/juce_VST3ClientExtensions.cpp | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp b/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp index b3cd133bb8..8cb875b114 100644 --- a/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp +++ b/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.cpp @@ -74,13 +74,39 @@ VST3ClientExtensions::InterfaceId VST3ClientExtensions::convertJucePluginId (uin return 0; }); - std::array data; - std::memcpy (&data[0], &word0, sizeof (word0)); - std::memcpy (&data[4], &word1, sizeof (word1)); - std::memcpy (&data[8], &manufacturerCode, sizeof (manufacturerCode)); - std::memcpy (&data[12], &pluginCode, sizeof (pluginCode)); + constexpr auto getByteFromLSB = [] (uint32_t word, int byteIndex) + { + jassert (isPositiveAndNotGreaterThan (byteIndex, 3)); + return (std::byte) ((word >> (byteIndex * 8)) & 0xff); + }; - return data; + #if JUCE_WINDOWS + constexpr auto isWindows = true; + #else + constexpr auto isWindows = false; + #endif + + return { + getByteFromLSB (word0, isWindows ? 0 : 3), + getByteFromLSB (word0, isWindows ? 1 : 2), + getByteFromLSB (word0, isWindows ? 2 : 1), + getByteFromLSB (word0, isWindows ? 3 : 0), + + getByteFromLSB (word1, isWindows ? 2 : 3), + getByteFromLSB (word1, isWindows ? 3 : 2), + getByteFromLSB (word1, isWindows ? 0 : 1), + getByteFromLSB (word1, isWindows ? 1 : 0), + + getByteFromLSB (manufacturerCode, 3), + getByteFromLSB (manufacturerCode, 2), + getByteFromLSB (manufacturerCode, 1), + getByteFromLSB (manufacturerCode, 0), + + getByteFromLSB (pluginCode, 3), + getByteFromLSB (pluginCode, 2), + getByteFromLSB (pluginCode, 1), + getByteFromLSB (pluginCode, 0) + }; } VST3ClientExtensions::InterfaceId VST3ClientExtensions::convertVST2PluginId (uint32_t pluginCode, From a889149cbdef11844c94d8a875d4b6f203f0b3da Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 2 Dec 2024 17:45:31 +0000 Subject: [PATCH 057/557] PixelRGB: Fix pixel order issue when creating CGImages on iOS --- modules/juce_graphics/colour/juce_PixelFormats.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/juce_graphics/colour/juce_PixelFormats.h b/modules/juce_graphics/colour/juce_PixelFormats.h index 7a17a87aa5..cd5bc4d168 100644 --- a/modules/juce_graphics/colour/juce_PixelFormats.h +++ b/modules/juce_graphics/colour/juce_PixelFormats.h @@ -556,7 +556,7 @@ public: //============================================================================== /** The indexes of the different components in the byte layout of this type of colour. */ - #if JUCE_MAC + #if JUCE_MAC || JUCE_IOS enum { indexR = 0, indexG = 1, indexB = 2 }; #else enum { indexR = 2, indexG = 1, indexB = 0 }; @@ -578,7 +578,7 @@ private: } //============================================================================== - #if JUCE_MAC + #if JUCE_MAC || JUCE_IOS uint8 r, g, b; #else uint8 b, g, r; From 5c138561bb08dffd3353c97c8720b4123e524f8a Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 2 Dec 2024 19:14:15 +0000 Subject: [PATCH 058/557] MidiDeviceListConnectionBroadcaster: Avoid constructing MessageManager on incorrect thread Previously, if the very first call to MidiDeviceListConnectionBroadcaster::get() happened on a background thread (which could happen on macOS in response to a MIDI setup configuration change), then MessageManager::getInstance and getAvailableDevices could be called on that same thread. With this change in place, midi change notifications will be ignored if there's no message manager available, and getAvailableDevices will only be called on the message thread. --- ...ce_MidiDeviceListConnectionBroadcaster.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/modules/juce_audio_devices/midi_io/juce_MidiDeviceListConnectionBroadcaster.cpp b/modules/juce_audio_devices/midi_io/juce_MidiDeviceListConnectionBroadcaster.cpp index 89e843d765..c80d2168de 100644 --- a/modules/juce_audio_devices/midi_io/juce_MidiDeviceListConnectionBroadcaster.cpp +++ b/modules/juce_audio_devices/midi_io/juce_MidiDeviceListConnectionBroadcaster.cpp @@ -57,20 +57,22 @@ public: void notify() { - if (MessageManager::getInstance()->isThisTheMessageThread()) - { - cancelPendingUpdate(); + auto* mm = MessageManager::getInstanceWithoutCreating(); - const State newState; + if (mm == nullptr) + return; - if (std::exchange (lastNotifiedState, newState) != newState) - for (auto it = callbacks.begin(); it != callbacks.end();) - NullCheckedInvocation::invoke ((it++)->second); - } - else + if (! mm->isThisTheMessageThread()) { triggerAsyncUpdate(); + return; } + + cancelPendingUpdate(); + + if (auto prev = std::exchange (lastNotifiedState, State{}); prev != lastNotifiedState) + for (auto it = callbacks.begin(); it != callbacks.end();) + NullCheckedInvocation::invoke ((it++)->second); } static auto& get() @@ -108,7 +110,7 @@ private: } std::map> callbacks; - State lastNotifiedState; + std::optional lastNotifiedState; MidiDeviceListConnection::Key key = 0; }; From 051e7017806d977162aaf746a78980ed3f63d3c7 Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 3 Dec 2024 14:26:21 +0000 Subject: [PATCH 059/557] Windowing: Update mousewheel handler on Windows to always process messages in the context of the peer receiving the event --- .../juce_gui_basics/native/juce_Windowing_windows.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index bf8d63c3fa..1e19b9df8c 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -2895,14 +2895,8 @@ private: // the window with focus, despite what the MSDN docs might say. // This is the behaviour we want; if we're receiving a scroll event, we can assume it // should be processed by the current peer. - const auto currentMousePos = getPOINTFromLParam ((LPARAM) GetMessagePos()); - auto* peer = getOwnerOfWindow (WindowFromPoint (currentMousePos)); - - if (peer == nullptr) - return false; - - const auto localPos = peer->globalToLocal (convertPhysicalScreenPointToLogical (D2DUtilities::toPoint (currentMousePos), hwnd).toFloat()); - peer->handleMouseWheel (getPointerType (wParam), localPos, getMouseEventTime(), wheel); + const auto localPos = getLocalPointFromScreenLParam ((LPARAM) GetMessagePos()); + handleMouseWheel (getPointerType (wParam), localPos, getMouseEventTime(), wheel); return true; } From 5878adaecd1b34896d70dbb2f9e9c2246b50b6de Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 21 Nov 2024 21:12:03 +0000 Subject: [PATCH 060/557] Projucer: Add android.permission.POST_NOTIFICATIONS to manifest when push notifications enabled --- .../DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml | 1 + .../Builds/Android/app/src/main/AndroidManifest.xml | 1 + .../Builds/Android/app/src/main/AndroidManifest.xml | 1 + .../Builds/Android/app/src/main/AndroidManifest.xml | 1 + .../Source/ProjectSaving/jucer_ProjectExport_Android.h | 3 +++ 5 files changed, 7 insertions(+) diff --git a/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml b/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml index 062c97cbde..d2797d5ca2 100644 --- a/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml +++ b/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + diff --git a/extras/AudioPerformanceTest/Builds/Android/app/src/main/AndroidManifest.xml b/extras/AudioPerformanceTest/Builds/Android/app/src/main/AndroidManifest.xml index 5b9c0d7266..f499caa9f6 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/src/main/AndroidManifest.xml +++ b/extras/AudioPerformanceTest/Builds/Android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + diff --git a/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml b/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml index f99a0ecc2c..7492a37f5b 100644 --- a/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml +++ b/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/AndroidManifest.xml b/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/AndroidManifest.xml index 7c000ddb7a..a34771de6d 100644 --- a/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/AndroidManifest.xml +++ b/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h index b36320e81d..8667a25400 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h @@ -1992,6 +1992,9 @@ private: if (androidVibratePermission.get()) s.add ("android.permission.VIBRATE"); + if (arePushNotificationsEnabled()) + s.add ("android.permission.POST_NOTIFICATIONS"); + return getCleanedStringArray (s); } From 0329635ed2df61be60fef56c81e348c733506227 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 21 Nov 2024 21:26:29 +0000 Subject: [PATCH 061/557] RuntimePermissions: Allow requesting the POST_NOTIFICATIONS permission at runtime on Android --- modules/juce_core/misc/juce_RuntimePermissions.h | 8 +++++++- .../juce_core/native/juce_RuntimePermissions_android.cpp | 7 +++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/juce_core/misc/juce_RuntimePermissions.h b/modules/juce_core/misc/juce_RuntimePermissions.h index 026e55778e..acd144f88b 100644 --- a/modules/juce_core/misc/juce_RuntimePermissions.h +++ b/modules/juce_core/misc/juce_RuntimePermissions.h @@ -113,7 +113,13 @@ public: /** Permission to read video files that your app didn't create. Has the same effect as readExternalStorage on iOS and Android versions before 33. */ - readMediaVideo = 8 + readMediaVideo = 8, + + /** Permission to post notifications. + + @see PushNotifications::requestPermissionsWithSettings + */ + postNotification = 9 }; //============================================================================== diff --git a/modules/juce_core/native/juce_RuntimePermissions_android.cpp b/modules/juce_core/native/juce_RuntimePermissions_android.cpp index d66d817b80..867bd2129c 100644 --- a/modules/juce_core/native/juce_RuntimePermissions_android.cpp +++ b/modules/juce_core/native/juce_RuntimePermissions_android.cpp @@ -81,6 +81,9 @@ static StringArray jucePermissionToAndroidPermissions (RuntimePermissions::Permi case RuntimePermissions::readMediaVideo: return { externalStorageOrMedia ("android.permission.READ_MEDIA_VIDEO") }; + + case RuntimePermissions::postNotification: + return { "android.permission.POST_NOTIFICATIONS" }; } // invalid permission @@ -101,6 +104,7 @@ static RuntimePermissions::PermissionID androidPermissionToJucePermission (const { "android.permission.READ_MEDIA_IMAGES", RuntimePermissions::readMediaImages }, { "android.permission.READ_MEDIA_VIDEO", RuntimePermissions::readMediaVideo }, { "android.permission.BLUETOOTH_SCAN", RuntimePermissions::bluetoothMidi }, + { "android.permission.POST_NOTIFICATIONS", RuntimePermissions::postNotification }, }; const auto iter = map.find (permission); @@ -118,8 +122,7 @@ struct PermissionsRequest //============================================================================== struct PermissionsOverlay final : public FragmentOverlay { - PermissionsOverlay (CriticalSection& cs) : overlayGuard (cs) {} - ~PermissionsOverlay() override = default; + explicit PermissionsOverlay (CriticalSection& cs) : overlayGuard (cs) {} struct PermissionResult { From 98031a814cbab00997854dddf2630c00b064b232 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 21 Nov 2024 21:57:40 +0000 Subject: [PATCH 062/557] PushNotifications: Remove unnecessary qualifications in Android impl --- .../native/juce_PushNotifications_android.cpp | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp b/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp index 1d77f03554..e824c4e296 100644 --- a/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp +++ b/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp @@ -296,7 +296,7 @@ struct PushNotifications::Pimpl } //============================================================================== - void sendLocalNotification (const PushNotifications::Notification& n) + void sendLocalNotification (const Notification& n) { // All required fields have to be setup! jassert (n.isValid()); @@ -322,7 +322,7 @@ struct PushNotifications::Pimpl { auto* env = getEnv(); - Array notifications; + Array notifications; auto notificationManager = getNotificationManager(); jassert (notificationManager != nullptr); @@ -601,7 +601,7 @@ struct PushNotifications::Pimpl javaString ("notification").get())); } - static LocalRef juceNotificationToJavaNotification (const PushNotifications::Notification& n) + static LocalRef juceNotificationToJavaNotification (const Notification& n) { auto* env = getEnv(); @@ -616,7 +616,7 @@ struct PushNotifications::Pimpl return LocalRef (env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.build)); } - static LocalRef createNotificationBuilder (const PushNotifications::Notification& n) + static LocalRef createNotificationBuilder (const Notification& n) { auto* env = getEnv(); LocalRef context (getMainActivity()); @@ -648,7 +648,7 @@ struct PushNotifications::Pimpl return LocalRef (env->NewObject (builderClass, builderConstructor, context.get())); } - static void setupRequiredFields (const PushNotifications::Notification& n, LocalRef& notificationBuilder) + static void setupRequiredFields (const Notification& n, LocalRef& notificationBuilder) { auto* env = getEnv(); LocalRef context (getMainActivity()); @@ -696,7 +696,7 @@ struct PushNotifications::Pimpl } } - static LocalRef juceNotificationToBundle (const PushNotifications::Notification& n) + static LocalRef juceNotificationToBundle (const Notification& n) { auto* env = getEnv(); @@ -753,7 +753,7 @@ struct PushNotifications::Pimpl return bundle; } - static void setupOptionalFields (const PushNotifications::Notification& n, LocalRef& notificationBuilder) + static void setupOptionalFields (const Notification& n, LocalRef& notificationBuilder) { auto* env = getEnv(); @@ -818,11 +818,11 @@ struct PushNotifications::Pimpl if (getAndroidSDKVersion() < 24) { - const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer; + const bool useChronometer = n.timestampVisibility == Notification::chronometer; env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer); } - const bool showTimeStamp = n.timestampVisibility != PushNotifications::Notification::off; + const bool showTimeStamp = n.timestampVisibility != Notification::off; env->CallObjectMethod (notificationBuilder, NotificationBuilderApi17.setShowWhen, showTimeStamp); if (n.groupId.isNotEmpty()) @@ -857,8 +857,8 @@ struct PushNotifications::Pimpl if (getAndroidSDKVersion() >= 24) { - const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer; - const bool useCountDownChronometer = n.timestampVisibility == PushNotifications::Notification::countDownChronometer; + const bool useChronometer = n.timestampVisibility == Notification::chronometer; + const bool useCountDownChronometer = n.timestampVisibility == Notification::countDownChronometer; env->CallObjectMethod (notificationBuilder, NotificationBuilderApi24.setChronometerCountDown, useCountDownChronometer); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer | useCountDownChronometer); @@ -874,7 +874,7 @@ struct PushNotifications::Pimpl setupNotificationDeletedCallback (n, notificationBuilder); } - static void setupNotificationDeletedCallback (const PushNotifications::Notification& n, + static void setupNotificationDeletedCallback (const Notification& n, LocalRef& notificationBuilder) { auto* env = getEnv(); @@ -900,7 +900,7 @@ struct PushNotifications::Pimpl env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDeleteIntent, deletePendingIntent.get()); } - static void setupActions (const PushNotifications::Notification& n, LocalRef& notificationBuilder) + static void setupActions (const Notification& n, LocalRef& notificationBuilder) { auto* env = getEnv(); LocalRef context (getMainActivity()); @@ -912,7 +912,7 @@ struct PushNotifications::Pimpl auto activityClass = LocalRef (env->CallObjectMethod (context.get(), JavaObject.getClass)); auto notifyIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get())); - const bool isTextStyle = action.style == PushNotifications::Notification::Action::text; + const bool isTextStyle = action.style == Notification::Action::text; auto packageNameString = LocalRef ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); const String notificationActionString = isTextStyle ? ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION." : ".JUCE_NOTIFICATION_BUTTON_ACTION."; @@ -1029,26 +1029,26 @@ struct PushNotifications::Pimpl return bitmap; } - static String typeToCategory (PushNotifications::Notification::Type t) + static String typeToCategory (Notification::Type t) { switch (t) { - case PushNotifications::Notification::unspecified: return {}; - case PushNotifications::Notification::alarm: return "alarm"; - case PushNotifications::Notification::call: return "call"; - case PushNotifications::Notification::email: return "email"; - case PushNotifications::Notification::error: return "err"; - case PushNotifications::Notification::event: return "event"; - case PushNotifications::Notification::message: return "msg"; - case PushNotifications::Notification::taskProgress: return "progress"; - case PushNotifications::Notification::promo: return "promo"; - case PushNotifications::Notification::recommendation: return "recommendation"; - case PushNotifications::Notification::reminder: return "reminder"; - case PushNotifications::Notification::service: return "service"; - case PushNotifications::Notification::social: return "social"; - case PushNotifications::Notification::status: return "status"; - case PushNotifications::Notification::system: return "sys"; - case PushNotifications::Notification::transport: return "transport"; + case Notification::unspecified: return {}; + case Notification::alarm: return "alarm"; + case Notification::call: return "call"; + case Notification::email: return "email"; + case Notification::error: return "err"; + case Notification::event: return "event"; + case Notification::message: return "msg"; + case Notification::taskProgress: return "progress"; + case Notification::promo: return "promo"; + case Notification::recommendation: return "recommendation"; + case Notification::reminder: return "reminder"; + case Notification::service: return "service"; + case Notification::social: return "social"; + case Notification::status: return "status"; + case Notification::system: return "sys"; + case Notification::transport: return "transport"; } return {}; @@ -1081,11 +1081,11 @@ struct PushNotifications::Pimpl } // Reverse of juceNotificationToBundle(). - static PushNotifications::Notification localNotificationBundleToJuceNotification (const LocalRef& bundle) + static Notification localNotificationBundleToJuceNotification (const LocalRef& bundle) { auto* env = getEnv(); - PushNotifications::Notification n; + Notification n; if (bundle.get() != nullptr) { @@ -1100,23 +1100,23 @@ struct PushNotifications::Pimpl n.icon = getStringFromBundle (env, "icon", bundle); n.channelId = getStringFromBundle (env, "channelId", bundle); - PushNotifications::Notification::Progress progress; + Notification::Progress progress; progress.max = getIntFromBundle (env, "progressMax", bundle); progress.current = getIntFromBundle (env, "progressCurrent", bundle); progress.indeterminate = getBoolFromBundle (env, "progressIndeterminate", bundle); n.progress = progress; n.person = getStringFromBundle (env, "person", bundle); - n.type = (PushNotifications::Notification::Type) getIntFromBundle (env, "type", bundle); - n.priority = (PushNotifications::Notification::Priority) getIntFromBundle (env, "priority", bundle); - n.lockScreenAppearance = (PushNotifications::Notification::LockScreenAppearance) getIntFromBundle (env, "lockScreenAppearance", bundle); + n.type = (Notification::Type) getIntFromBundle (env, "type", bundle); + n.priority = (Notification::Priority) getIntFromBundle (env, "priority", bundle); + n.lockScreenAppearance = (Notification::LockScreenAppearance) getIntFromBundle (env, "lockScreenAppearance", bundle); n.groupId = getStringFromBundle (env, "groupId", bundle); n.groupSortKey = getStringFromBundle (env, "groupSortKey", bundle); n.groupSummary = getBoolFromBundle (env, "groupSummary", bundle); n.accentColour = Colour ((uint32) getIntFromBundle (env, "accentColour", bundle)); n.ledColour = Colour ((uint32) getIntFromBundle (env, "ledColour", bundle)); - PushNotifications::Notification::LedBlinkPattern ledBlinkPattern; + Notification::LedBlinkPattern ledBlinkPattern; ledBlinkPattern.msToBeOn = getIntFromBundle (env, "ledBlinkPatternMsToBeOn", bundle); ledBlinkPattern.msToBeOff = getIntFromBundle (env, "ledBlinkPatternMsToBeOff", bundle); n.ledBlinkPattern = ledBlinkPattern; @@ -1127,9 +1127,9 @@ struct PushNotifications::Pimpl n.localOnly = getBoolFromBundle (env, "localOnly", bundle); n.ongoing = getBoolFromBundle (env, "ongoing", bundle); n.alertOnlyOnce = getBoolFromBundle (env, "alertOnlyOnce", bundle); - n.timestampVisibility = (PushNotifications::Notification::TimestampVisibility) getIntFromBundle (env, "timestampVisibility", bundle); - n.badgeIconType = (PushNotifications::Notification::BadgeIconType) getIntFromBundle (env, "badgeIconType", bundle); - n.groupAlertBehaviour = (PushNotifications::Notification::GroupAlertBehaviour) getIntFromBundle (env, "groupAlertBehaviour", bundle); + n.timestampVisibility = (Notification::TimestampVisibility) getIntFromBundle (env, "timestampVisibility", bundle); + n.badgeIconType = (Notification::BadgeIconType) getIntFromBundle (env, "badgeIconType", bundle); + n.groupAlertBehaviour = (Notification::GroupAlertBehaviour) getIntFromBundle (env, "groupAlertBehaviour", bundle); n.timeoutAfterMs = getLongFromBundle (env, "timeoutAfterMs", bundle); } @@ -1216,7 +1216,7 @@ struct PushNotifications::Pimpl return {}; } - static PushNotifications::Notification javaNotificationToJuceNotification (const LocalRef& notification) + static Notification javaNotificationToJuceNotification (const LocalRef& notification) { auto* env = getEnv(); @@ -1230,10 +1230,10 @@ struct PushNotifications::Pimpl return remoteNotificationBundleToJuceNotification (extras); } - static PushNotifications::Notification remoteNotificationBundleToJuceNotification (const LocalRef& bundle) + static Notification remoteNotificationBundleToJuceNotification (const LocalRef& bundle) { // This will probably work only for remote notifications that get delivered to system tray - PushNotifications::Notification n; + Notification n; n.properties = bundleToVar (bundle); return n; @@ -1282,7 +1282,7 @@ struct PushNotifications::Pimpl } #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - static PushNotifications::Notification firebaseRemoteNotificationToJuceNotification (jobject remoteNotification) + static Notification firebaseRemoteNotificationToJuceNotification (jobject remoteNotification) { auto* env = getEnv(); @@ -1325,7 +1325,7 @@ struct PushNotifications::Pimpl propertiesDynamicObject->setProperty ("ttl", ttl); propertiesDynamicObject->setProperty ("data", dataVar); - PushNotifications::Notification n; + Notification n; if (notification != 0) { From ed0092a8bc9685830c8ce87347b8eae13558e9be Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 21 Nov 2024 22:06:42 +0000 Subject: [PATCH 063/557] PushNotifications: Assert instead of crashing if Android notification icon cannot be located --- .../native/juce_PushNotifications_android.cpp | 92 +++++++++++++------ 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp b/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp index e824c4e296..fcbefabd1d 100644 --- a/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp +++ b/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp @@ -276,7 +276,7 @@ bool PushNotifications::Notification::isValid() const noexcept //============================================================================== struct PushNotifications::Pimpl { - Pimpl (PushNotifications& p) + explicit Pimpl (PushNotifications& p) : owner (p) {} @@ -303,16 +303,18 @@ struct PushNotifications::Pimpl auto* env = getEnv(); - auto notificationManager = getNotificationManager(); - - if (notificationManager.get() != nullptr) + if (auto notificationManager = getNotificationManager()) { - auto notification = juceNotificationToJavaNotification (n); + if (auto notification = juceNotificationToJavaNotification (n)) + { + auto tag = javaString (n.identifier); + const int id = 0; - auto tag = javaString (n.identifier); - const int id = 0; - - env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.notify, tag.get(), id, notification.get()); + env->CallVoidMethod (notificationManager.get(), + NotificationManagerBase.notify, + tag.get(), + id, notification.get()); + } } } @@ -607,11 +609,12 @@ struct PushNotifications::Pimpl auto notificationBuilder = createNotificationBuilder (n); - setupRequiredFields (n, notificationBuilder); - setupOptionalFields (n, notificationBuilder); + notificationBuilder = setupRequiredFields (n, notificationBuilder); + notificationBuilder = setupOptionalFields (n, notificationBuilder); + notificationBuilder = setupActions (n, notificationBuilder); - if (n.actions.size() > 0) - setupActions (n, notificationBuilder); + if (notificationBuilder == nullptr) + return notificationBuilder; return LocalRef (env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.build)); } @@ -648,8 +651,11 @@ struct PushNotifications::Pimpl return LocalRef (env->NewObject (builderClass, builderConstructor, context.get())); } - static void setupRequiredFields (const Notification& n, LocalRef& notificationBuilder) + static LocalRef setupRequiredFields (const Notification& n, LocalRef notificationBuilder) { + if (notificationBuilder == nullptr) + return notificationBuilder; + auto* env = getEnv(); LocalRef context (getMainActivity()); @@ -676,8 +682,16 @@ struct PushNotifications::Pimpl env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentIntent, notifyPendingIntent.get()); auto resources = LocalRef (env->CallObjectMethod (context.get(), AndroidContext.getResources)); - const int iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(), - javaString ("raw").get(), packageNameString.get()); + const auto iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(), + javaString ("raw").get(), packageNameString.get()); + + if (iconId == 0) + { + // If you hit this, the notification icon could not be located, and the notification + // will not be sent. + jassertfalse; + return {}; + } env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setSmallIcon, iconId); @@ -688,12 +702,17 @@ struct PushNotifications::Pimpl auto publicNotificationBuilder = createNotificationBuilder (n); - setupRequiredFields (*n.publicVersion, publicNotificationBuilder); - setupOptionalFields (*n.publicVersion, publicNotificationBuilder); + publicNotificationBuilder = setupRequiredFields (*n.publicVersion, publicNotificationBuilder); + publicNotificationBuilder = setupOptionalFields (*n.publicVersion, publicNotificationBuilder); + + if (publicNotificationBuilder == nullptr) + return {}; auto publicVersion = LocalRef (env->CallObjectMethod (publicNotificationBuilder, NotificationBuilderApi16.build)); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setPublicVersion, publicVersion.get()); } + + return notificationBuilder; } static LocalRef juceNotificationToBundle (const Notification& n) @@ -753,8 +772,11 @@ struct PushNotifications::Pimpl return bundle; } - static void setupOptionalFields (const Notification& n, LocalRef& notificationBuilder) + static LocalRef setupOptionalFields (const Notification n, LocalRef& notificationBuilder) { + if (notificationBuilder == nullptr) + return notificationBuilder; + auto* env = getEnv(); if (n.subtitle.isNotEmpty()) @@ -871,12 +893,15 @@ struct PushNotifications::Pimpl env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setTimeoutAfter, (jlong) n.timeoutAfterMs); } - setupNotificationDeletedCallback (n, notificationBuilder); + return setupNotificationDeletedCallback (n, notificationBuilder); } - static void setupNotificationDeletedCallback (const Notification& n, - LocalRef& notificationBuilder) + static LocalRef setupNotificationDeletedCallback (const Notification& n, + LocalRef notificationBuilder) { + if (notificationBuilder == nullptr) + return notificationBuilder; + auto* env = getEnv(); LocalRef context (getMainActivity()); @@ -898,16 +923,19 @@ struct PushNotifications::Pimpl 0)); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDeleteIntent, deletePendingIntent.get()); + + return notificationBuilder; } - static void setupActions (const Notification& n, LocalRef& notificationBuilder) + static LocalRef setupActions (const Notification& n, LocalRef notificationBuilder) { + if (notificationBuilder == nullptr || n.actions.isEmpty()) + return notificationBuilder; + auto* env = getEnv(); LocalRef context (getMainActivity()); - int actionIndex = 0; - - for (const auto& action : n.actions) + for (const auto [actionIndex, action] : enumerate (n.actions)) { auto activityClass = LocalRef (env->CallObjectMethod (context.get(), JavaObject.getClass)); auto notifyIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get())); @@ -938,6 +966,14 @@ struct PushNotifications::Pimpl iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(), javaString ("raw").get(), packageNameString.get()); + if (iconId == 0) + { + // If this is hit, the notification icon could not be located, so the notification + // cannot be displayed. + jassertfalse; + return {}; + } + auto actionBuilder = LocalRef (env->NewObject (NotificationActionBuilder, NotificationActionBuilder.constructor, iconId, @@ -982,9 +1018,9 @@ struct PushNotifications::Pimpl env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addAction, env->CallObjectMethod (actionBuilder, NotificationActionBuilder.build)); - - ++actionIndex; } + + return notificationBuilder; } static LocalRef juceUrlToAndroidUri (const URL& url) From 6d10eb536fc10da8981b25aa868c1714a6431453 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 25 Nov 2024 13:31:51 +0000 Subject: [PATCH 064/557] PushNotifications: Update Android implementation to properly request permissions --- .../native/juce_PushNotifications_android.cpp | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp b/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp index fcbefabd1d..46c50123c8 100644 --- a/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp +++ b/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp @@ -282,6 +282,9 @@ struct PushNotifications::Pimpl bool areNotificationsEnabled() const { + if (getAndroidSDKVersion() >= 33 && ! RuntimePermissions::isGranted (RuntimePermissions::postNotification)) + return false; + if (getAndroidSDKVersion() >= 24) { auto* env = getEnv(); @@ -296,6 +299,28 @@ struct PushNotifications::Pimpl } //============================================================================== + void requestPermissionsWithSettings (const Settings&) + { + RuntimePermissions::request (RuntimePermissions::postNotification, [&] (bool) + { + const auto notifyListeners = [] + { + if (auto* instance = PushNotifications::getInstance()) + instance->listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); }); + }; + + if (MessageManager::getInstance()->isThisTheMessageThread()) + notifyListeners(); + else + MessageManager::callAsync (notifyListeners); + }); + } + + void requestSettingsUsed() + { + owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); }); + } + void sendLocalNotification (const Notification& n) { // All required fields have to be setup! @@ -651,6 +676,8 @@ struct PushNotifications::Pimpl return LocalRef (env->NewObject (builderClass, builderConstructor, context.get())); } + static constexpr auto FLAG_IMMUTABLE = 0x04000000; + static LocalRef setupRequiredFields (const Notification& n, LocalRef notificationBuilder) { if (notificationBuilder == nullptr) @@ -675,7 +702,7 @@ struct PushNotifications::Pimpl context.get(), 1002, notifyIntent.get(), - 0)); + FLAG_IMMUTABLE)); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentTitle, javaString (n.title).get()); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentText, javaString (n.body).get()); @@ -920,7 +947,7 @@ struct PushNotifications::Pimpl context.get(), 1002, deleteIntent.get(), - 0)); + FLAG_IMMUTABLE)); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDeleteIntent, deletePendingIntent.get()); @@ -956,7 +983,7 @@ struct PushNotifications::Pimpl context.get(), 1002, notifyIntent.get(), - 0)); + FLAG_IMMUTABLE)); auto resources = LocalRef (env->CallObjectMethod (context.get(), AndroidContext.getResources)); int iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (action.icon).get(), From 269ebbb52568e8cab700d0f3a16a479721dfb3bb Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 21 Nov 2024 21:26:55 +0000 Subject: [PATCH 065/557] Accessibility: Add AccessibilityHandler::postSystemNotification() function for posting an OS-specific accessible notification --- .../juce_AccessibilityHandler.cpp | 16 +- .../accessibility/juce_AccessibilityHandler.h | 27 ++- .../juce_Accessibility_android.cpp | 5 + .../accessibility/juce_Accessibility_ios.mm | 5 + .../accessibility/juce_Accessibility_mac.mm | 5 + .../juce_Accessibility_windows.cpp | 144 +++++++------ .../misc/juce_PushNotifications.cpp | 200 +++++++++++++----- .../misc/juce_PushNotifications.h | 12 +- .../native/juce_PushNotifications_android.cpp | 25 ++- .../native/juce_PushNotifications_ios.cpp | 12 +- .../native/juce_PushNotifications_mac.cpp | 6 +- 11 files changed, 308 insertions(+), 149 deletions(-) diff --git a/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp b/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp index a6ddb49633..3e6b1af91b 100644 --- a/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp +++ b/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp @@ -35,8 +35,6 @@ namespace juce { -AccessibilityHandler* AccessibilityHandler::currentlyFocusedHandler = nullptr; - class NativeChildHandler { public: @@ -403,10 +401,24 @@ void AccessibilityHandler::setNativeChildForComponent (Component& component, voi NativeChildHandler::getInstance().setNativeChild (component, nativeChild); } +#if JUCE_MODULE_AVAILABLE_juce_gui_extra +void privatePostSystemNotification (const String&, const String&); +#endif + +void AccessibilityHandler::postSystemNotification ([[maybe_unused]] const String& notificationTitle, + [[maybe_unused]] const String& notificationBody) +{ + #if JUCE_MODULE_AVAILABLE_juce_gui_extra + if (areAnyAccessibilityClientsActive()) + privatePostSystemNotification (notificationTitle, notificationBody); + #endif +} + #if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {} void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {} AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; } + bool AccessibilityHandler::areAnyAccessibilityClientsActive() { return false; } #endif } // namespace juce diff --git a/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h b/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h index da8b3f3af4..540799839b 100644 --- a/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h +++ b/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h @@ -295,6 +295,30 @@ public: */ static void postAnnouncement (const String& announcementString, AnnouncementPriority priority); + /** Posts a local system notification. + + In order for this to do anything, the following conditions must be met. + - At build time: + - The juce_gui_extra module must be included in the project. + - Push notifications must be enabled by setting the preprocessor definition + JUCE_PUSH_NOTIFICATIONS=1 + - At run time: + - An accessibility client (narrator, voiceover etc.) must be active. + + Additionally, on Android, an icon is required for notifications. + This must be specified by adding the path to the icon file called + "accessibilitynotificationicon" in the "Extra Android Raw Resources" setting + in the Projucer. + + This will use the push notification client on macOS, iOS and Android. + On Windows this will create a system tray icon to post the notification. + + @param notificationTitle the title of the notification + @param notificationBody the main body text of the notification + */ + static void postSystemNotification (const String& notificationTitle, + const String& notificationBody); + //============================================================================== /** @internal */ AccessibilityNativeHandle* getNativeImplementation() const; @@ -329,8 +353,9 @@ private: void grabFocusInternal (bool); void giveAwayFocusInternal() const; void takeFocus(); + static bool areAnyAccessibilityClientsActive(); - static AccessibilityHandler* currentlyFocusedHandler; + static inline AccessibilityHandler* currentlyFocusedHandler = nullptr; //============================================================================== Component& component; diff --git a/modules/juce_gui_basics/native/accessibility/juce_Accessibility_android.cpp b/modules/juce_gui_basics/native/accessibility/juce_Accessibility_android.cpp index 4ab7bd9db8..418eba3108 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_Accessibility_android.cpp +++ b/modules/juce_gui_basics/native/accessibility/juce_Accessibility_android.cpp @@ -1055,4 +1055,9 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, javaString (announcementString).get()); } +bool AccessibilityHandler::areAnyAccessibilityClientsActive() +{ + return AccessibilityNativeHandle::areAnyAccessibilityClientsActive(); +} + } // namespace juce diff --git a/modules/juce_gui_basics/native/accessibility/juce_Accessibility_ios.mm b/modules/juce_gui_basics/native/accessibility/juce_Accessibility_ios.mm index 21538ae777..5c4643b499 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_Accessibility_ios.mm +++ b/modules/juce_gui_basics/native/accessibility/juce_Accessibility_ios.mm @@ -666,4 +666,9 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString)); } +bool AccessibilityHandler::areAnyAccessibilityClientsActive() +{ + return juce::areAnyAccessibilityClientsActive(); +} + } // namespace juce diff --git a/modules/juce_gui_basics/native/accessibility/juce_Accessibility_mac.mm b/modules/juce_gui_basics/native/accessibility/juce_Accessibility_mac.mm index 48647846ad..a6001b00e7 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_Accessibility_mac.mm +++ b/modules/juce_gui_basics/native/accessibility/juce_Accessibility_mac.mm @@ -939,4 +939,9 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A NSAccessibilityPriorityKey: @(nsPriority) }); } +bool AccessibilityHandler::areAnyAccessibilityClientsActive() +{ + return juce::areAnyAccessibilityClientsActive(); +} + } // namespace juce diff --git a/modules/juce_gui_basics/native/accessibility/juce_Accessibility_windows.cpp b/modules/juce_gui_basics/native/accessibility/juce_Accessibility_windows.cpp index 0c98af66dd..9fa451fb4e 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_Accessibility_windows.cpp +++ b/modules/juce_gui_basics/native/accessibility/juce_Accessibility_windows.cpp @@ -39,26 +39,83 @@ namespace juce JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") -static bool isStartingUpOrShuttingDown() +//============================================================================== +struct WindowsAccessibility { - if (auto* app = JUCEApplicationBase::getInstance()) - if (app->isInitialising()) + WindowsAccessibility() = delete; + + static long getUiaRootObjectId() + { + return static_cast (UiaRootObjectId); + } + + static bool handleWmGetObject (AccessibilityHandler* handler, WPARAM wParam, LPARAM lParam, LRESULT* res) + { + if (isStartingUpOrShuttingDown() || (handler == nullptr || ! isHandlerValid (*handler))) + return false; + + if (auto* uiaWrapper = WindowsUIAWrapper::getInstance()) + { + ComSmartPtr provider; + handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())); + + if (! uiaWrapper->isProviderDisconnecting (provider)) + *res = uiaWrapper->returnRawElementProvider ((HWND) handler->getComponent().getWindowHandle(), wParam, lParam, provider); + return true; + } - if (auto* mm = MessageManager::getInstanceWithoutCreating()) - if (mm->hasStopMessageBeenSent()) - return true; + return false; + } - return false; -} + static void revokeUIAMapEntriesForWindow (HWND hwnd) + { + if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) + uiaWrapper->returnRawElementProvider (hwnd, 0, 0, nullptr); + } -static bool isHandlerValid (const AccessibilityHandler& handler) -{ - if (auto* provider = handler.getNativeImplementation()) - return provider->isElementValid(); + static bool isStartingUpOrShuttingDown() + { + if (auto* app = JUCEApplicationBase::getInstance()) + if (app->isInitialising()) + return true; - return false; -} + if (auto* mm = MessageManager::getInstanceWithoutCreating()) + if (mm->hasStopMessageBeenSent()) + return true; + + return false; + } + + static bool isHandlerValid (const AccessibilityHandler& handler) + { + if (auto* provider = handler.getNativeImplementation()) + return provider->isElementValid(); + + return false; + } + + static bool areAnyAccessibilityClientsActive() + { + const auto areClientsListening = [] + { + if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) + return uiaWrapper->clientsAreListening() != 0; + + return false; + }; + + const auto isScreenReaderRunning = [] + { + BOOL isRunning = FALSE; + SystemParametersInfo (SPI_GETSCREENREADER, 0, (PVOID) &isRunning, 0); + + return isRunning != 0; + }; + + return areClientsListening() || isScreenReaderRunning(); + } +}; //============================================================================== class AccessibilityHandler::AccessibilityNativeImpl @@ -103,31 +160,12 @@ AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const return nativeImpl->accessibilityElement; } -static bool areAnyAccessibilityClientsActive() -{ - const auto areClientsListening = [] - { - if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) - return uiaWrapper->clientsAreListening() != 0; - - return false; - }; - - const auto isScreenReaderRunning = [] - { - BOOL isRunning = FALSE; - SystemParametersInfo (SPI_GETSCREENREADER, 0, (PVOID) &isRunning, 0); - - return isRunning != 0; - }; - - return areClientsListening() || isScreenReaderRunning(); -} - template void getProviderWithCheckedWrapper (const AccessibilityHandler& handler, Callback&& callback) { - if (! areAnyAccessibilityClientsActive() || isStartingUpOrShuttingDown() || ! isHandlerValid (handler)) + if (! WindowsAccessibility::areAnyAccessibilityClientsActive() + || WindowsAccessibility::isStartingUpOrShuttingDown() + || ! WindowsAccessibility::isHandlerValid (handler)) return; if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) @@ -292,41 +330,11 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A } } -//============================================================================== -namespace WindowsAccessibility +bool AccessibilityHandler::areAnyAccessibilityClientsActive() { - static long getUiaRootObjectId() - { - return static_cast (UiaRootObjectId); - } - - static bool handleWmGetObject (AccessibilityHandler* handler, WPARAM wParam, LPARAM lParam, LRESULT* res) - { - if (isStartingUpOrShuttingDown() || (handler == nullptr || ! isHandlerValid (*handler))) - return false; - - if (auto* uiaWrapper = WindowsUIAWrapper::getInstance()) - { - ComSmartPtr provider; - handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())); - - if (! uiaWrapper->isProviderDisconnecting (provider)) - *res = uiaWrapper->returnRawElementProvider ((HWND) handler->getComponent().getWindowHandle(), wParam, lParam, provider); - - return true; - } - - return false; - } - - static void revokeUIAMapEntriesForWindow (HWND hwnd) - { - if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) - uiaWrapper->returnRawElementProvider (hwnd, 0, 0, nullptr); - } + return WindowsAccessibility::areAnyAccessibilityClientsActive(); } - JUCE_END_IGNORE_WARNINGS_GCC_LIKE } // namespace juce diff --git a/modules/juce_gui_extra/misc/juce_PushNotifications.cpp b/modules/juce_gui_extra/misc/juce_PushNotifications.cpp index 4b6bca78be..c6d4ee426e 100644 --- a/modules/juce_gui_extra/misc/juce_PushNotifications.cpp +++ b/modules/juce_gui_extra/misc/juce_PushNotifications.cpp @@ -36,10 +36,50 @@ namespace juce { //============================================================================== -#if ! JUCE_ANDROID && ! JUCE_IOS && ! JUCE_MAC +#if ! JUCE_PUSH_NOTIFICATIONS_IMPL + +struct PushNotifications::Impl +{ + explicit Impl (PushNotifications& o) : owner (o) {} + + void requestPermissionsWithSettings (const Settings&) const + { + owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); }); + } + + void requestSettingsUsed() const + { + owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); }); + } + + bool areNotificationsEnabled() const { return false; } + void getDeliveredNotifications() const {} + void removeAllDeliveredNotifications() const {} + String getDeviceToken() const { return {}; } + void setupChannels (const Array&, const Array&) const {} + void getPendingLocalNotifications() const {} + void removeAllPendingLocalNotifications() const {} + void subscribeToTopic (const String&) const {} + void unsubscribeFromTopic (const String&) const {} + void sendLocalNotification (const Notification&) const {} + void removeDeliveredNotification (const String&) const {} + void removePendingLocalNotification (const String&) const {} + void sendUpstreamMessage (const String&, + const String&, + const String&, + const String&, + int, + const StringPairArray&) const {} + +private: + PushNotifications& owner; +}; + bool PushNotifications::Notification::isValid() const noexcept { return true; } + #endif +//============================================================================== PushNotifications::Notification::Notification (const Notification& other) : identifier (other.identifier), title (other.title), @@ -82,9 +122,7 @@ PushNotifications::Notification::Notification (const Notification& other) //============================================================================== PushNotifications::PushNotifications() - #if JUCE_PUSH_NOTIFICATIONS - : pimpl (new Pimpl (*this)) - #endif + : pimpl (new Impl (*this)) { } @@ -93,128 +131,90 @@ PushNotifications::~PushNotifications() { clearSingletonInstance(); } void PushNotifications::addListener (Listener* l) { listeners.add (l); } void PushNotifications::removeListener (Listener* l) { listeners.remove (l); } -void PushNotifications::requestPermissionsWithSettings ([[maybe_unused]] const PushNotifications::Settings& settings) +void PushNotifications::requestPermissionsWithSettings (const Settings& settings) { - #if JUCE_PUSH_NOTIFICATIONS && (JUCE_IOS || JUCE_MAC) pimpl->requestPermissionsWithSettings (settings); - #else - listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); }); - #endif } void PushNotifications::requestSettingsUsed() { - #if JUCE_PUSH_NOTIFICATIONS && (JUCE_IOS || JUCE_MAC) pimpl->requestSettingsUsed(); - #else - listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); }); - #endif } bool PushNotifications::areNotificationsEnabled() const { - #if JUCE_PUSH_NOTIFICATIONS return pimpl->areNotificationsEnabled(); - #else - return false; - #endif } void PushNotifications::getDeliveredNotifications() const { - #if JUCE_PUSH_NOTIFICATIONS pimpl->getDeliveredNotifications(); - #endif } void PushNotifications::removeAllDeliveredNotifications() { - #if JUCE_PUSH_NOTIFICATIONS pimpl->removeAllDeliveredNotifications(); - #endif } String PushNotifications::getDeviceToken() const { - #if JUCE_PUSH_NOTIFICATIONS return pimpl->getDeviceToken(); - #else - return {}; - #endif } -void PushNotifications::setupChannels ([[maybe_unused]] const Array& groups, [[maybe_unused]] const Array& channels) +void PushNotifications::setupChannels (const Array& groups, + const Array& channels) { - #if JUCE_PUSH_NOTIFICATIONS pimpl->setupChannels (groups, channels); - #endif } void PushNotifications::getPendingLocalNotifications() const { - #if JUCE_PUSH_NOTIFICATIONS pimpl->getPendingLocalNotifications(); - #endif } void PushNotifications::removeAllPendingLocalNotifications() { - #if JUCE_PUSH_NOTIFICATIONS pimpl->removeAllPendingLocalNotifications(); - #endif } -void PushNotifications::subscribeToTopic ([[maybe_unused]] const String& topic) +void PushNotifications::subscribeToTopic (const String& topic) { - #if JUCE_PUSH_NOTIFICATIONS pimpl->subscribeToTopic (topic); - #endif } -void PushNotifications::unsubscribeFromTopic ([[maybe_unused]] const String& topic) +void PushNotifications::unsubscribeFromTopic (const String& topic) { - #if JUCE_PUSH_NOTIFICATIONS pimpl->unsubscribeFromTopic (topic); - #endif } - -void PushNotifications::sendLocalNotification ([[maybe_unused]] const Notification& n) +void PushNotifications::sendLocalNotification (const Notification& n) { - #if JUCE_PUSH_NOTIFICATIONS pimpl->sendLocalNotification (n); - #endif } -void PushNotifications::removeDeliveredNotification ([[maybe_unused]] const String& identifier) +void PushNotifications::removeDeliveredNotification (const String& identifier) { - #if JUCE_PUSH_NOTIFICATIONS pimpl->removeDeliveredNotification (identifier); - #endif } -void PushNotifications::removePendingLocalNotification ([[maybe_unused]] const String& identifier) +void PushNotifications::removePendingLocalNotification (const String& identifier) { - #if JUCE_PUSH_NOTIFICATIONS pimpl->removePendingLocalNotification (identifier); - #endif } -void PushNotifications::sendUpstreamMessage ([[maybe_unused]] const String& serverSenderId, - [[maybe_unused]] const String& collapseKey, - [[maybe_unused]] const String& messageId, - [[maybe_unused]] const String& messageType, - [[maybe_unused]] int timeToLive, - [[maybe_unused]] const StringPairArray& additionalData) +void PushNotifications::sendUpstreamMessage (const String& serverSenderId, + const String& collapseKey, + const String& messageId, + const String& messageType, + int timeToLive, + const StringPairArray& additionalData) { - #if JUCE_PUSH_NOTIFICATIONS pimpl->sendUpstreamMessage (serverSenderId, collapseKey, messageId, messageType, timeToLive, additionalData); - #endif } //============================================================================== @@ -234,4 +234,92 @@ void PushNotifications::Listener::upstreamMessageSent ([[maybe_unused]] const St void PushNotifications::Listener::upstreamMessageSendingError ([[maybe_unused]] const String& messageId, [[maybe_unused]] const String& error) {} +//============================================================================== +void privatePostSystemNotification (const String& notificationTitle, const String& notificationBody); +void privatePostSystemNotification ([[maybe_unused]] const String& notificationTitle, + [[maybe_unused]] const String& notificationBody) +{ + #if JUCE_PUSH_NOTIFICATIONS + #if JUCE_ANDROID || JUCE_IOS || JUCE_MAC + auto* notificationsInstance = PushNotifications::getInstance(); + + if (notificationsInstance == nullptr) + return; + + #if JUCE_ANDROID + notificationsInstance->requestPermissionsWithSettings ({}); + + static auto channels = std::invoke ([]() -> Array + { + PushNotifications::Channel chan; + + chan.identifier = "1"; + chan.name = "Notifications"; + chan.description = "Accessibility notifications"; + chan.groupId = "accessibility"; + chan.ledColour = Colours::yellow; + chan.canShowBadge = true; + chan.enableLights = true; + chan.enableVibration = true; + chan.soundToPlay = URL ("default_os_sound"); + chan.vibrationPattern = { 1000, 1000 }; + + return { chan }; + }); + + notificationsInstance->setupChannels ({ PushNotifications::ChannelGroup { "accessibility", "accessibility" } }, + channels); + #else + static auto settings = std::invoke ([] + { + PushNotifications::Settings s; + s.allowAlert = true; + s.allowBadge = true; + s.allowSound = true; + + #if JUCE_IOS + PushNotifications::Settings::Category c; + c.identifier = "Accessibility"; + + s.categories = { c }; + #endif + + return s; + }); + + notificationsInstance->requestPermissionsWithSettings (settings); + #endif + + const auto notification = std::invoke ([¬ificationTitle, ¬ificationBody] + { + PushNotifications::Notification n; + + n.identifier = String (Random::getSystemRandom().nextInt()); + n.title = notificationTitle; + n.body = notificationBody; + + #if JUCE_IOS + n.category = "Accessibility"; + #elif JUCE_ANDROID + n.channelId = "1"; + n.icon = "accessibilitynotificationicon"; + #endif + + return n; + }); + + if (notification.isValid()) + notificationsInstance->sendLocalNotification (notification); + + #else + SystemTrayIconComponent systemTrayIcon; + + Image im (Image::ARGB, 128, 128, true); + systemTrayIcon.setIconImage (im, im); + + systemTrayIcon.showInfoBubble (notificationTitle, notificationBody); + #endif + #endif +} + } // namespace juce diff --git a/modules/juce_gui_extra/misc/juce_PushNotifications.h b/modules/juce_gui_extra/misc/juce_PushNotifications.h index b3d7b550b1..b5bafe2511 100644 --- a/modules/juce_gui_extra/misc/juce_PushNotifications.h +++ b/modules/juce_gui_extra/misc/juce_PushNotifications.h @@ -403,8 +403,8 @@ public: */ struct Category { - juce::String identifier; /**< unique identifier */ - juce::Array actions; /**< optional list of actions within this category */ + String identifier; /**< unique identifier */ + Array actions; /**< optional list of actions within this category */ bool sendDismissAction = false; /**< whether dismiss action will be sent to the app */ }; @@ -703,12 +703,8 @@ private: friend struct JuceFirebaseMessagingService; #endif - #if JUCE_PUSH_NOTIFICATIONS - struct Pimpl; - friend struct Pimpl; - - std::unique_ptr pimpl; - #endif + struct Impl; + std::unique_ptr pimpl; }; } // namespace juce diff --git a/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp b/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp index 46c50123c8..25dd16027f 100644 --- a/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp +++ b/modules/juce_gui_extra/native/juce_PushNotifications_android.cpp @@ -35,6 +35,8 @@ namespace juce { +#define JUCE_PUSH_NOTIFICATIONS_IMPL 1 + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \ METHOD (enableLights, "enableLights", "(Z)V") \ @@ -274,9 +276,9 @@ bool PushNotifications::Notification::isValid() const noexcept } //============================================================================== -struct PushNotifications::Pimpl +struct PushNotifications::Impl { - explicit Pimpl (PushNotifications& p) + explicit Impl (PushNotifications& p) : owner (p) {} @@ -306,7 +308,7 @@ struct PushNotifications::Pimpl const auto notifyListeners = [] { if (auto* instance = PushNotifications::getInstance()) - instance->listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); }); + instance->listeners.call ([] (Listener& l) { l.notificationSettingsReceived (makeDefaultSettings()); }); }; if (MessageManager::getInstance()->isThisTheMessageThread()) @@ -318,7 +320,7 @@ struct PushNotifications::Pimpl void requestSettingsUsed() { - owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); }); + owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived (makeDefaultSettings()); }); } void sendLocalNotification (const Notification& n) @@ -1573,6 +1575,15 @@ struct PushNotifications::Pimpl && env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.message_id").get()); } + static Settings makeDefaultSettings() + { + Settings settings; + settings.allowAlert = true; + settings.allowBadge = true; + settings.allowSound = true; + return settings; + } + PushNotifications& owner; }; @@ -1640,14 +1651,14 @@ bool juce_handleNotificationIntent (void* intent) { auto* instance = PushNotifications::getInstanceWithoutCreating(); - if (PushNotifications::Pimpl::isDeleteNotificationIntent ((jobject) intent)) + if (PushNotifications::Impl::isDeleteNotificationIntent ((jobject) intent)) { if (instance) instance->pimpl->notifyListenersAboutLocalNotificationDeleted (LocalRef ((jobject) intent)); return true; } - else if (PushNotifications::Pimpl::isLocalNotificationIntent ((jobject) intent)) + else if (PushNotifications::Impl::isLocalNotificationIntent ((jobject) intent)) { if (instance) instance->pimpl->notifyListenersAboutLocalNotification (LocalRef ((jobject) intent)); @@ -1655,7 +1666,7 @@ bool juce_handleNotificationIntent (void* intent) return true; } #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - else if (PushNotifications::Pimpl::isRemoteNotificationIntent ((jobject) intent)) + else if (PushNotifications::Impl::isRemoteNotificationIntent ((jobject) intent)) { if (instance) instance->pimpl->notifyListenersAboutRemoteNotificationFromSystemTray (LocalRef ((jobject) intent)); diff --git a/modules/juce_gui_extra/native/juce_PushNotifications_ios.cpp b/modules/juce_gui_extra/native/juce_PushNotifications_ios.cpp index b093a76434..a2d44c7c7e 100644 --- a/modules/juce_gui_extra/native/juce_PushNotifications_ios.cpp +++ b/modules/juce_gui_extra/native/juce_PushNotifications_ios.cpp @@ -35,6 +35,8 @@ namespace juce { +#define JUCE_PUSH_NOTIFICATIONS_IMPL 1 + struct PushNotificationsDelegateDetails { //============================================================================== @@ -301,9 +303,9 @@ bool PushNotifications::Notification::isValid() const noexcept } //============================================================================== -struct PushNotifications::Pimpl +struct PushNotifications::Impl { - Pimpl (PushNotifications& p) + explicit Impl (PushNotifications& p) : owner (p) { Class::setThis (delegate.get(), this); @@ -555,7 +557,7 @@ private: Class() : ObjCClass ("JucePushNotificationsDelegate_") { - addIvar ("self"); + addIvar ("self"); addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), [] (id self, SEL, UIApplication*, NSData* data) { @@ -596,8 +598,8 @@ private: } //============================================================================== - static Pimpl& getThis (id self) { return *getIvar (self, "self"); } - static void setThis (id self, Pimpl* d) { object_setInstanceVariable (self, "self", d); } + static Impl& getThis (id self) { return *getIvar (self, "self"); } + static void setThis (id self, Impl* d) { object_setInstanceVariable (self, "self", d); } }; //============================================================================== diff --git a/modules/juce_gui_extra/native/juce_PushNotifications_mac.cpp b/modules/juce_gui_extra/native/juce_PushNotifications_mac.cpp index c994beb3bd..a06dd82dc9 100644 --- a/modules/juce_gui_extra/native/juce_PushNotifications_mac.cpp +++ b/modules/juce_gui_extra/native/juce_PushNotifications_mac.cpp @@ -35,6 +35,8 @@ namespace juce { +#define JUCE_PUSH_NOTIFICATIONS_IMPL 1 + JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS namespace PushNotificationsDelegateDetailsOsx @@ -343,9 +345,9 @@ private: bool PushNotifications::Notification::isValid() const noexcept { return true; } //============================================================================== -struct PushNotifications::Pimpl : private PushNotificationsDelegate +struct PushNotifications::Impl : private PushNotificationsDelegate { - Pimpl (PushNotifications& p) + explicit Impl (PushNotifications& p) : owner (p) { } From 5d5829927a896c388b0896b3da6321905cc11492 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 25 Nov 2024 13:29:34 +0000 Subject: [PATCH 066/557] AccessibilityDemo: Add local notifications example Also updates the DemoRunner so that the new push notifications example works properly on Android. --- .../Builds/Android/app/CMakeLists.txt | 4 +- .../res/raw/accessibilitynotificationicon.png | Bin 0 -> 103448 bytes .../res/raw/accessibilitynotificationicon.png | Bin 0 -> 103448 bytes .../DemoRunner/Builds/LinuxMakefile/Makefile | 4 +- .../DemoRunner.xcodeproj/project.pbxproj | 2 + .../VisualStudio2019/DemoRunner_App.vcxproj | 8 +- .../VisualStudio2022/DemoRunner_App.vcxproj | 8 +- .../iOS/DemoRunner.xcodeproj/project.pbxproj | 2 + examples/DemoRunner/CMakeLists.txt | 1 + examples/DemoRunner/DemoRunner.jucer | 6 +- .../Source/accessibilitynotificationicon.png | Bin 0 -> 103448 bytes examples/GUI/AccessibilityDemo.h | 209 +++++++++++++----- 12 files changed, 168 insertions(+), 76 deletions(-) create mode 100644 examples/DemoRunner/Builds/Android/app/src/debug/res/raw/accessibilitynotificationicon.png create mode 100644 examples/DemoRunner/Builds/Android/app/src/release/res/raw/accessibilitynotificationicon.png create mode 100644 examples/DemoRunner/Source/accessibilitynotificationicon.png diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt index 23f6137865..31974123bb 100644 --- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt +++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt @@ -34,9 +34,9 @@ include_directories( AFTER enable_language(ASM) if(JUCE_BUILD_CONFIGURATION MATCHES "DEBUG") - add_definitions([[-DJUCE_PROJUCER_VERSION=0x80004]] [[-DJUCE_MODULE_AVAILABLE_juce_analytics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_animation=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1]] [[-DJUCE_MODULE_AVAILABLE_juce_box2d=1]] [[-DJUCE_MODULE_AVAILABLE_juce_core=1]] [[-DJUCE_MODULE_AVAILABLE_juce_cryptography=1]] [[-DJUCE_MODULE_AVAILABLE_juce_data_structures=1]] [[-DJUCE_MODULE_AVAILABLE_juce_dsp=1]] [[-DJUCE_MODULE_AVAILABLE_juce_events=1]] [[-DJUCE_MODULE_AVAILABLE_juce_graphics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1]] [[-DJUCE_MODULE_AVAILABLE_juce_javascript=1]] [[-DJUCE_MODULE_AVAILABLE_juce_opengl=1]] [[-DJUCE_MODULE_AVAILABLE_juce_osc=1]] [[-DJUCE_MODULE_AVAILABLE_juce_product_unlocking=1]] [[-DJUCE_MODULE_AVAILABLE_juce_video=1]] [[-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1]] [[-DJUCE_USE_MP3AUDIOFORMAT=1]] [[-DJUCE_PLUGINHOST_VST3=1]] [[-DJUCE_PLUGINHOST_LV2=1]] [[-DJUCE_ALLOW_STATIC_NULL_VARIABLES=0]] [[-DJUCE_STRICT_REFCOUNTEDPOINTER=1]] [[-DJUCE_USE_CAMERA=1]] [[-DJUCE_STANDALONE_APPLICATION=1]] [[-DJUCE_DEMO_RUNNER=1]] [[-DJUCE_UNIT_TESTS=1]] [[-DJUCER_ANDROIDSTUDIO_7F0E4A25=1]] [[-DJUCE_APP_VERSION=8.0.4]] [[-DJUCE_APP_VERSION_HEX=0x80004]] [[-DDEBUG=1]] [[-D_DEBUG=1]]) + add_definitions([[-DJUCE_PROJUCER_VERSION=0x80004]] [[-DJUCE_MODULE_AVAILABLE_juce_analytics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_animation=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1]] [[-DJUCE_MODULE_AVAILABLE_juce_box2d=1]] [[-DJUCE_MODULE_AVAILABLE_juce_core=1]] [[-DJUCE_MODULE_AVAILABLE_juce_cryptography=1]] [[-DJUCE_MODULE_AVAILABLE_juce_data_structures=1]] [[-DJUCE_MODULE_AVAILABLE_juce_dsp=1]] [[-DJUCE_MODULE_AVAILABLE_juce_events=1]] [[-DJUCE_MODULE_AVAILABLE_juce_graphics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1]] [[-DJUCE_MODULE_AVAILABLE_juce_javascript=1]] [[-DJUCE_MODULE_AVAILABLE_juce_opengl=1]] [[-DJUCE_MODULE_AVAILABLE_juce_osc=1]] [[-DJUCE_MODULE_AVAILABLE_juce_product_unlocking=1]] [[-DJUCE_MODULE_AVAILABLE_juce_video=1]] [[-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1]] [[-DJUCE_USE_MP3AUDIOFORMAT=1]] [[-DJUCE_PLUGINHOST_VST3=1]] [[-DJUCE_PLUGINHOST_LV2=1]] [[-DJUCE_ALLOW_STATIC_NULL_VARIABLES=0]] [[-DJUCE_STRICT_REFCOUNTEDPOINTER=1]] [[-DJUCE_USE_CAMERA=1]] [[-DJUCE_STANDALONE_APPLICATION=1]] [[-DJUCE_DEMO_RUNNER=1]] [[-DJUCE_UNIT_TESTS=1]] [[-DJUCE_PUSH_NOTIFICATIONS=1]] [[-DJUCER_ANDROIDSTUDIO_7F0E4A25=1]] [[-DJUCE_APP_VERSION=8.0.4]] [[-DJUCE_APP_VERSION_HEX=0x80004]] [[-DDEBUG=1]] [[-D_DEBUG=1]]) elseif(JUCE_BUILD_CONFIGURATION MATCHES "RELEASE") - add_definitions([[-DJUCE_PROJUCER_VERSION=0x80004]] [[-DJUCE_MODULE_AVAILABLE_juce_analytics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_animation=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1]] [[-DJUCE_MODULE_AVAILABLE_juce_box2d=1]] [[-DJUCE_MODULE_AVAILABLE_juce_core=1]] [[-DJUCE_MODULE_AVAILABLE_juce_cryptography=1]] [[-DJUCE_MODULE_AVAILABLE_juce_data_structures=1]] [[-DJUCE_MODULE_AVAILABLE_juce_dsp=1]] [[-DJUCE_MODULE_AVAILABLE_juce_events=1]] [[-DJUCE_MODULE_AVAILABLE_juce_graphics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1]] [[-DJUCE_MODULE_AVAILABLE_juce_javascript=1]] [[-DJUCE_MODULE_AVAILABLE_juce_opengl=1]] [[-DJUCE_MODULE_AVAILABLE_juce_osc=1]] [[-DJUCE_MODULE_AVAILABLE_juce_product_unlocking=1]] [[-DJUCE_MODULE_AVAILABLE_juce_video=1]] [[-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1]] [[-DJUCE_USE_MP3AUDIOFORMAT=1]] [[-DJUCE_PLUGINHOST_VST3=1]] [[-DJUCE_PLUGINHOST_LV2=1]] [[-DJUCE_ALLOW_STATIC_NULL_VARIABLES=0]] [[-DJUCE_STRICT_REFCOUNTEDPOINTER=1]] [[-DJUCE_USE_CAMERA=1]] [[-DJUCE_STANDALONE_APPLICATION=1]] [[-DJUCE_DEMO_RUNNER=1]] [[-DJUCE_UNIT_TESTS=1]] [[-DJUCER_ANDROIDSTUDIO_7F0E4A25=1]] [[-DJUCE_APP_VERSION=8.0.4]] [[-DJUCE_APP_VERSION_HEX=0x80004]] [[-DNDEBUG=1]]) + add_definitions([[-DJUCE_PROJUCER_VERSION=0x80004]] [[-DJUCE_MODULE_AVAILABLE_juce_analytics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_animation=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1]] [[-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1]] [[-DJUCE_MODULE_AVAILABLE_juce_box2d=1]] [[-DJUCE_MODULE_AVAILABLE_juce_core=1]] [[-DJUCE_MODULE_AVAILABLE_juce_cryptography=1]] [[-DJUCE_MODULE_AVAILABLE_juce_data_structures=1]] [[-DJUCE_MODULE_AVAILABLE_juce_dsp=1]] [[-DJUCE_MODULE_AVAILABLE_juce_events=1]] [[-DJUCE_MODULE_AVAILABLE_juce_graphics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1]] [[-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1]] [[-DJUCE_MODULE_AVAILABLE_juce_javascript=1]] [[-DJUCE_MODULE_AVAILABLE_juce_opengl=1]] [[-DJUCE_MODULE_AVAILABLE_juce_osc=1]] [[-DJUCE_MODULE_AVAILABLE_juce_product_unlocking=1]] [[-DJUCE_MODULE_AVAILABLE_juce_video=1]] [[-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1]] [[-DJUCE_USE_MP3AUDIOFORMAT=1]] [[-DJUCE_PLUGINHOST_VST3=1]] [[-DJUCE_PLUGINHOST_LV2=1]] [[-DJUCE_ALLOW_STATIC_NULL_VARIABLES=0]] [[-DJUCE_STRICT_REFCOUNTEDPOINTER=1]] [[-DJUCE_USE_CAMERA=1]] [[-DJUCE_STANDALONE_APPLICATION=1]] [[-DJUCE_DEMO_RUNNER=1]] [[-DJUCE_UNIT_TESTS=1]] [[-DJUCE_PUSH_NOTIFICATIONS=1]] [[-DJUCER_ANDROIDSTUDIO_7F0E4A25=1]] [[-DJUCE_APP_VERSION=8.0.4]] [[-DJUCE_APP_VERSION_HEX=0x80004]] [[-DNDEBUG=1]]) else() message( FATAL_ERROR "No matching build-configuration found." ) endif() diff --git a/examples/DemoRunner/Builds/Android/app/src/debug/res/raw/accessibilitynotificationicon.png b/examples/DemoRunner/Builds/Android/app/src/debug/res/raw/accessibilitynotificationicon.png new file mode 100644 index 0000000000000000000000000000000000000000..30b40d4d2805c575ed5dd518b82baa13874c5c03 GIT binary patch literal 103448 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