Signed-off-by: falkTX <falktx@falktx.com>tags/v2.5.0
| @@ -1,28 +0,0 @@ | |||
| #!/bin/bash | |||
| set -e | |||
| JUCE_MODULES_DIR="/Shared/Personal/FOSS/GIT/DISTRHO/juce/modules" | |||
| CARLA_MODULES_DIR="/Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules" | |||
| MODULES=("juce_audio_basics juce_audio_devices juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") | |||
| for M in $MODULES; do | |||
| echo $CARLA_MODULES_DIR/$M; | |||
| rm -f $CARLA_MODULES_DIR/$M/juce_* | |||
| rm -rf $CARLA_MODULES_DIR/$M/{a..z}* | |||
| cp -r -v $JUCE_MODULES_DIR/$M/* $CARLA_MODULES_DIR/$M/ | |||
| rm $CARLA_MODULES_DIR/$M/juce_*.mm | |||
| done | |||
| find $CARLA_MODULES_DIR -name juce_module_info -delete | |||
| rm -rf $CARLA_MODULES_DIR/juce_*/native/java/ | |||
| rm -rf $CARLA_MODULES_DIR/juce_*/native/javacore/ | |||
| rm -rf $CARLA_MODULES_DIR/juce_*/native/javaopt/ | |||
| rm -rf $CARLA_MODULES_DIR/juce_*/native/oboe/ | |||
| sed -i "s|Juce VST Host|Carla Plugin Host|" $CARLA_MODULES_DIR/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp | |||
| rm -rf $CARLA_MODULES_DIR/../includes/vst3sdk | |||
| mv $CARLA_MODULES_DIR/juce_audio_processors/format_types/VST3_SDK $CARLA_MODULES_DIR/../includes/vst3sdk | |||
| rm -rf $CARLA_MODULES_DIR/../includes/vst3sdk/*.pdf | |||
| @@ -0,0 +1,33 @@ | |||
| #!/bin/bash | |||
| set -e | |||
| cd $(dirname ${0}) | |||
| cd .. | |||
| CARLA_DIR=$(pwd) | |||
| DISTRHO_PORTS_DIR=/tmp/distrho-ports-carla | |||
| rm -rf ${DISTRHO_PORTS_DIR} | |||
| git clone git@github.com:DISTRHO/DISTRHO-Ports.git ${DISTRHO_PORTS_DIR} --depth=1 | |||
| CARLA_MODULES_DIR=${CARLA_DIR}/source/modules | |||
| JUCE_MODULES_DIR=${DISTRHO_PORTS_DIR}/libs/juce7/source/modules | |||
| MODULES=("juce_audio_basics juce_audio_devices juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") | |||
| for M in ${MODULES}; do | |||
| echo ${CARLA_MODULES_DIR}/${M}; | |||
| rm -f ${CARLA_MODULES_DIR}/${M}/juce_* | |||
| rm -rf ${CARLA_MODULES_DIR}/${M}/{a..z}* | |||
| cp -r -v ${JUCE_MODULES_DIR}/${M}/* ${CARLA_MODULES_DIR}/${M}/ | |||
| rm ${CARLA_MODULES_DIR}/${M}/juce_*.mm | |||
| done | |||
| find ${CARLA_MODULES_DIR} -name juce_module_info -delete | |||
| rm -rf ${CARLA_MODULES_DIR}/../includes/vst3sdk | |||
| mv ${CARLA_MODULES_DIR}/juce_audio_processors/format_types/VST3_SDK ${CARLA_MODULES_DIR}/../includes/vst3sdk | |||
| rm -rf ${CARLA_MODULES_DIR}/../includes/vst3sdk/*.pdf | |||
| # rm -rf ${DISTRHO_PORTS_DIR} | |||
| @@ -503,9 +503,12 @@ public: | |||
| ? (AEffect*)fInstance->getPlatformSpecificData() | |||
| : nullptr; | |||
| /* TODO update to juce7 APIs | |||
| v3_plugin_view** const vst3view = fDesc.pluginFormatName == "VST3" | |||
| ? (v3_plugin_view**)editor->getPlatformSpecificData() | |||
| : nullptr; | |||
| */ | |||
| v3_plugin_view** const vst3view = nullptr; | |||
| fWindow = new JucePluginWindow(opts.frontendWinId, opts.pluginsAreStandalone, | |||
| vst2effect, vst3view); | |||
| @@ -1553,10 +1556,12 @@ protected: | |||
| pData->engine->touchPluginParameter(pData->id, static_cast<uint32_t>(index), false); | |||
| } | |||
| bool getCurrentPosition(CurrentPositionInfo& result) override | |||
| juce::Optional<juce::AudioPlayHead::PositionInfo> getPosition() const override | |||
| { | |||
| /* TODO update to juce7 APIs | |||
| carla_copyStruct(result, fPosInfo); | |||
| return true; | |||
| */ | |||
| return {}; | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| @@ -203,6 +203,8 @@ | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // juce_audio_processors | |||
| #define JUCE_VST_FALLBACK_HOST_NAME "Carla" | |||
| //============================================================================= | |||
| /** Config: JUCE_PLUGINHOST_VST | |||
| Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be | |||
| @@ -152,8 +152,60 @@ public: | |||
| bool drop = false, pulldown = false; | |||
| }; | |||
| /** Describes a musical time signature. | |||
| @see PositionInfo::getTimeSignature() PositionInfo::setTimeSignature() | |||
| */ | |||
| struct JUCE_API TimeSignature | |||
| { | |||
| /** Time signature numerator, e.g. the 3 of a 3/4 time sig */ | |||
| int numerator = 4; | |||
| /** Time signature denominator, e.g. the 4 of a 3/4 time sig */ | |||
| int denominator = 4; | |||
| bool operator== (const TimeSignature& other) const | |||
| { | |||
| const auto tie = [] (auto& x) { return std::tie (x.numerator, x.denominator); }; | |||
| return tie (*this) == tie (other); | |||
| } | |||
| bool operator!= (const TimeSignature& other) const | |||
| { | |||
| return ! operator== (other); | |||
| } | |||
| }; | |||
| /** Holds the begin and end points of a looped region. | |||
| @see PositionInfo::getIsLooping() PositionInfo::setIsLooping() PositionInfo::getLoopPoints() PositionInfo::setLoopPoints() | |||
| */ | |||
| struct JUCE_API LoopPoints | |||
| { | |||
| /** The current cycle start position in units of quarter-notes. */ | |||
| double ppqStart = 0; | |||
| /** The current cycle end position in units of quarter-notes. */ | |||
| double ppqEnd = 0; | |||
| bool operator== (const LoopPoints& other) const | |||
| { | |||
| const auto tie = [] (auto& x) { return std::tie (x.ppqStart, x.ppqEnd); }; | |||
| return tie (*this) == tie (other); | |||
| } | |||
| bool operator!= (const LoopPoints& other) const | |||
| { | |||
| return ! operator== (other); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| /** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method. | |||
| /** This type is deprecated; prefer PositionInfo instead. | |||
| Some position info may be unavailable, depending on the host or plugin format. | |||
| Unfortunately, CurrentPositionInfo doesn't have any way of differentiating between | |||
| default values and values that have been set explicitly. | |||
| */ | |||
| struct JUCE_API CurrentPositionInfo | |||
| { | |||
| @@ -162,6 +214,7 @@ public: | |||
| /** Time signature numerator, e.g. the 3 of a 3/4 time sig */ | |||
| int timeSigNumerator = 4; | |||
| /** Time signature denominator, e.g. the 4 of a 3/4 time sig */ | |||
| int timeSigDenominator = 4; | |||
| @@ -248,7 +301,208 @@ public: | |||
| }; | |||
| //============================================================================== | |||
| /** Fills-in the given structure with details about the transport's | |||
| /** | |||
| Describes the time at the start of the current audio callback. | |||
| Not all hosts and plugin formats can provide all of the possible time | |||
| information, so most of the getter functions in this class return | |||
| an Optional that will only be engaged if the host provides the corresponding | |||
| information. As a plugin developer, you should code defensively so that | |||
| the plugin behaves sensibly even when the host fails to provide timing | |||
| information. | |||
| A default-constructed instance of this class will return nullopt from | |||
| all functions that return an Optional. | |||
| */ | |||
| class PositionInfo | |||
| { | |||
| public: | |||
| /** Returns the number of samples that have elapsed. */ | |||
| Optional<int64_t> getTimeInSamples() const { return getOptional (flagTimeSamples, timeInSamples); } | |||
| /** @see getTimeInSamples() */ | |||
| void setTimeInSamples (Optional<int64_t> timeInSamplesIn) { setOptional (flagTimeSamples, timeInSamples, timeInSamplesIn); } | |||
| /** Returns the number of seconds that have elapsed. */ | |||
| Optional<double> getTimeInSeconds() const { return getOptional (flagTimeSeconds, timeInSeconds); } | |||
| /** @see getTimeInSamples() */ | |||
| void setTimeInSeconds (Optional<double> timeInSecondsIn) { setOptional (flagTimeSeconds, timeInSeconds, timeInSecondsIn); } | |||
| /** Returns the bpm, if available. */ | |||
| Optional<double> getBpm() const { return getOptional (flagTempo, tempoBpm); } | |||
| /** @see getBpm() */ | |||
| void setBpm (Optional<double> bpmIn) { setOptional (flagTempo, tempoBpm, bpmIn); } | |||
| /** Returns the time signature, if available. */ | |||
| Optional<TimeSignature> getTimeSignature() const { return getOptional (flagTimeSignature, timeSignature); } | |||
| /** @see getTimeSignature() */ | |||
| void setTimeSignature (Optional<TimeSignature> timeSignatureIn) { setOptional (flagTimeSignature, timeSignature, timeSignatureIn); } | |||
| /** Returns host loop points, if available. */ | |||
| Optional<LoopPoints> getLoopPoints() const { return getOptional (flagLoopPoints, loopPoints); } | |||
| /** @see getLoopPoints() */ | |||
| void setLoopPoints (Optional<LoopPoints> loopPointsIn) { setOptional (flagLoopPoints, loopPoints, loopPointsIn); } | |||
| /** The number of bars since the beginning of the timeline. | |||
| This value isn't available in all hosts or in all plugin formats. | |||
| */ | |||
| Optional<int64_t> getBarCount() const { return getOptional (flagBarCount, barCount); } | |||
| /** @see getBarCount() */ | |||
| void setBarCount (Optional<int64_t> barCountIn) { setOptional (flagBarCount, barCount, barCountIn); } | |||
| /** The position of the start of the last bar, in units of quarter-notes. | |||
| This is the time from the start of the timeline to the start of the current | |||
| bar, in ppq units. | |||
| Note - this value may be unavailable on some hosts, e.g. Pro-Tools. | |||
| */ | |||
| Optional<double> getPpqPositionOfLastBarStart() const { return getOptional (flagLastBarStartPpq, lastBarStartPpq); } | |||
| /** @see getPpqPositionOfLastBarStart() */ | |||
| void setPpqPositionOfLastBarStart (Optional<double> positionIn) { setOptional (flagLastBarStartPpq, lastBarStartPpq, positionIn); } | |||
| /** The video frame rate, if available. */ | |||
| Optional<FrameRate> getFrameRate() const { return getOptional (flagFrameRate, frame); } | |||
| /** @see getFrameRate() */ | |||
| void setFrameRate (Optional<FrameRate> frameRateIn) { setOptional (flagFrameRate, frame, frameRateIn); } | |||
| /** The current play position, in units of quarter-notes. */ | |||
| Optional<double> getPpqPosition() const { return getOptional (flagPpqPosition, positionPpq); } | |||
| /** @see getPpqPosition() */ | |||
| void setPpqPosition (Optional<double> ppqPositionIn) { setOptional (flagPpqPosition, positionPpq, ppqPositionIn); } | |||
| /** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */ | |||
| Optional<double> getEditOriginTime() const { return getOptional (flagOriginTime, originTime); } | |||
| /** @see getEditOriginTime() */ | |||
| void setEditOriginTime (Optional<double> editOriginTimeIn) { setOptional (flagOriginTime, originTime, editOriginTimeIn); } | |||
| /** Get the host's callback time in nanoseconds, if available. */ | |||
| Optional<uint64_t> getHostTimeNs() const { return getOptional (flagHostTimeNs, hostTimeNs); } | |||
| /** @see getHostTimeNs() */ | |||
| void setHostTimeNs (Optional<uint64_t> hostTimeNsIn) { setOptional (flagHostTimeNs, hostTimeNs, hostTimeNsIn); } | |||
| /** True if the transport is currently playing. */ | |||
| bool getIsPlaying() const { return getFlag (flagIsPlaying); } | |||
| /** @see getIsPlaying() */ | |||
| void setIsPlaying (bool isPlayingIn) { setFlag (flagIsPlaying, isPlayingIn); } | |||
| /** True if the transport is currently recording. | |||
| (When isRecording is true, then isPlaying will also be true). | |||
| */ | |||
| bool getIsRecording() const { return getFlag (flagIsRecording); } | |||
| /** @see getIsRecording() */ | |||
| void setIsRecording (bool isRecordingIn) { setFlag (flagIsRecording, isRecordingIn); } | |||
| /** True if the transport is currently looping. */ | |||
| bool getIsLooping() const { return getFlag (flagIsLooping); } | |||
| /** @see getIsLooping() */ | |||
| void setIsLooping (bool isLoopingIn) { setFlag (flagIsLooping, isLoopingIn); } | |||
| bool operator== (const PositionInfo& other) const noexcept | |||
| { | |||
| const auto tie = [] (const PositionInfo& i) | |||
| { | |||
| return std::make_tuple (i.getTimeInSamples(), | |||
| i.getTimeInSeconds(), | |||
| i.getPpqPosition(), | |||
| i.getEditOriginTime(), | |||
| i.getPpqPositionOfLastBarStart(), | |||
| i.getFrameRate(), | |||
| i.getBarCount(), | |||
| i.getTimeSignature(), | |||
| i.getBpm(), | |||
| i.getLoopPoints(), | |||
| i.getHostTimeNs(), | |||
| i.getIsPlaying(), | |||
| i.getIsRecording(), | |||
| i.getIsLooping()); | |||
| }; | |||
| return tie (*this) == tie (other); | |||
| } | |||
| bool operator!= (const PositionInfo& other) const noexcept | |||
| { | |||
| return ! operator== (other); | |||
| } | |||
| private: | |||
| bool getFlag (int64_t flagToCheck) const | |||
| { | |||
| return (flagToCheck & flags) != 0; | |||
| } | |||
| void setFlag (int64_t flagToCheck, bool value) | |||
| { | |||
| flags = (value ? flags | flagToCheck : flags & ~flagToCheck); | |||
| } | |||
| template <typename Value> | |||
| Optional<Value> getOptional (int64_t flagToCheck, Value value) const | |||
| { | |||
| return getFlag (flagToCheck) ? makeOptional (std::move (value)) : nullopt; | |||
| } | |||
| template <typename Value> | |||
| void setOptional (int64_t flagToCheck, Value& value, Optional<Value> opt) | |||
| { | |||
| if (opt.hasValue()) | |||
| value = *opt; | |||
| setFlag (flagToCheck, opt.hasValue()); | |||
| } | |||
| enum | |||
| { | |||
| flagTimeSignature = 1 << 0, | |||
| flagLoopPoints = 1 << 1, | |||
| flagFrameRate = 1 << 2, | |||
| flagTimeSeconds = 1 << 3, | |||
| flagLastBarStartPpq = 1 << 4, | |||
| flagPpqPosition = 1 << 5, | |||
| flagOriginTime = 1 << 6, | |||
| flagTempo = 1 << 7, | |||
| flagTimeSamples = 1 << 8, | |||
| flagBarCount = 1 << 9, | |||
| flagHostTimeNs = 1 << 10, | |||
| flagIsPlaying = 1 << 11, | |||
| flagIsRecording = 1 << 12, | |||
| flagIsLooping = 1 << 13 | |||
| }; | |||
| TimeSignature timeSignature; | |||
| LoopPoints loopPoints; | |||
| FrameRate frame = FrameRateType::fps23976; | |||
| double timeInSeconds = 0.0; | |||
| double lastBarStartPpq = 0.0; | |||
| double positionPpq = 0.0; | |||
| double originTime = 0.0; | |||
| double tempoBpm = 0.0; | |||
| int64_t timeInSamples = 0; | |||
| int64_t barCount = 0; | |||
| uint64_t hostTimeNs = 0; | |||
| int64_t flags = 0; | |||
| }; | |||
| //============================================================================== | |||
| /** Deprecated, use getPosition() instead. | |||
| Fills-in the given structure with details about the transport's | |||
| position at the start of the current processing block. If this method returns | |||
| false then the current play head position is not available and the given | |||
| structure will be undefined. | |||
| @@ -258,7 +512,70 @@ public: | |||
| in which a time would make sense, and some hosts will almost certainly have | |||
| multithreading issues if it's not called on the audio thread. | |||
| */ | |||
| virtual bool getCurrentPosition (CurrentPositionInfo& result) = 0; | |||
| [[deprecated ("Use getPosition instead. Not all hosts are able to provide all time position information; getPosition differentiates clearly between set and unset fields.")]] | |||
| bool getCurrentPosition (CurrentPositionInfo& result) | |||
| { | |||
| if (const auto pos = getPosition()) | |||
| { | |||
| result.resetToDefault(); | |||
| if (const auto sig = pos->getTimeSignature()) | |||
| { | |||
| result.timeSigNumerator = sig->numerator; | |||
| result.timeSigDenominator = sig->denominator; | |||
| } | |||
| if (const auto loop = pos->getLoopPoints()) | |||
| { | |||
| result.ppqLoopStart = loop->ppqStart; | |||
| result.ppqLoopEnd = loop->ppqEnd; | |||
| } | |||
| if (const auto frame = pos->getFrameRate()) | |||
| result.frameRate = *frame; | |||
| if (const auto timeInSeconds = pos->getTimeInSeconds()) | |||
| result.timeInSeconds = *timeInSeconds; | |||
| if (const auto lastBarStartPpq = pos->getPpqPositionOfLastBarStart()) | |||
| result.ppqPositionOfLastBarStart = *lastBarStartPpq; | |||
| if (const auto ppqPosition = pos->getPpqPosition()) | |||
| result.ppqPosition = *ppqPosition; | |||
| if (const auto originTime = pos->getEditOriginTime()) | |||
| result.editOriginTime = *originTime; | |||
| if (const auto bpm = pos->getBpm()) | |||
| result.bpm = *bpm; | |||
| if (const auto timeInSamples = pos->getTimeInSamples()) | |||
| result.timeInSamples = *timeInSamples; | |||
| result.isPlaying = pos->getIsPlaying(); | |||
| result.isRecording = pos->getIsRecording(); | |||
| result.isLooping = pos->getIsLooping(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| /** Fetches details about the transport's position at the start of the current | |||
| processing block. If this method returns nullopt then the current play head | |||
| position is not available. | |||
| A non-null return value just indicates that the host was able to provide | |||
| *some* relevant timing information. Individual PositionInfo getters may | |||
| still return nullopt. | |||
| You can ONLY call this from your processBlock() method! Calling it at other | |||
| times will produce undefined behaviour, as the host may not have any context | |||
| in which a time would make sense, and some hosts will almost certainly have | |||
| multithreading issues if it's not called on the audio thread. | |||
| */ | |||
| virtual Optional<PositionInfo> getPosition() const = 0; | |||
| /** Returns true if this object can control the transport. */ | |||
| virtual bool canControlTransport() { return false; } | |||
| @@ -23,53 +23,6 @@ | |||
| namespace juce | |||
| { | |||
| #ifndef DOXYGEN | |||
| /** The contents of this namespace are used to implement AudioBuffer and should | |||
| not be used elsewhere. Their interfaces (and existence) are liable to change! | |||
| */ | |||
| namespace detail | |||
| { | |||
| /** On iOS/arm7 the alignment of `double` is greater than the alignment of | |||
| `std::max_align_t`, so we can't trust max_align_t. Instead, we query | |||
| lots of primitive types and use the maximum alignment of all of them. | |||
| We're putting this stuff outside AudioBuffer itself to avoid creating | |||
| unnecessary copies for each distinct template instantiation of | |||
| AudioBuffer. | |||
| MSVC 2015 doesn't like when we write getMaxAlignment as a loop which | |||
| accumulates the max alignment (declarations not allowed in constexpr | |||
| function body) so instead we use this recursive version which | |||
| instantiates a zillion templates. | |||
| */ | |||
| template <typename> struct Type {}; | |||
| constexpr size_t getMaxAlignment() noexcept { return 0; } | |||
| template <typename Head, typename... Tail> | |||
| constexpr size_t getMaxAlignment (Type<Head>, Type<Tail>... tail) noexcept | |||
| { | |||
| return jmax (alignof (Head), getMaxAlignment (tail...)); | |||
| } | |||
| constexpr size_t maxAlignment = getMaxAlignment (Type<std::max_align_t>{}, | |||
| Type<void*>{}, | |||
| Type<float>{}, | |||
| Type<double>{}, | |||
| Type<long double>{}, | |||
| Type<short int>{}, | |||
| Type<int>{}, | |||
| Type<long int>{}, | |||
| Type<long long int>{}, | |||
| Type<bool>{}, | |||
| Type<char>{}, | |||
| Type<char16_t>{}, | |||
| Type<char32_t>{}, | |||
| Type<wchar_t>{}); | |||
| } // namespace detail | |||
| #endif | |||
| //============================================================================== | |||
| /** | |||
| A multi-channel buffer containing floating point audio samples. | |||
| @@ -1215,17 +1168,10 @@ public: | |||
| private: | |||
| //============================================================================== | |||
| int numChannels = 0, size = 0; | |||
| size_t allocatedBytes = 0; | |||
| Type** channels; | |||
| HeapBlock<char, true> allocatedData; | |||
| Type* preallocatedChannelSpace[32]; | |||
| bool isClear = false; | |||
| void allocateData() | |||
| { | |||
| #if (! JUCE_GCC || (__GNUC__ * 100 + __GNUC_MINOR__) >= 409) | |||
| static_assert (alignof (Type) <= detail::maxAlignment, | |||
| static_assert (alignof (Type) <= maxAlignment, | |||
| "AudioBuffer cannot hold types with alignment requirements larger than that guaranteed by malloc"); | |||
| #endif | |||
| jassert (size >= 0); | |||
| @@ -1278,6 +1224,43 @@ private: | |||
| isClear = false; | |||
| } | |||
| /* On iOS/arm7 the alignment of `double` is greater than the alignment of | |||
| `std::max_align_t`, so we can't trust max_align_t. Instead, we query | |||
| lots of primitive types and use the maximum alignment of all of them. | |||
| */ | |||
| static constexpr size_t getMaxAlignment() noexcept | |||
| { | |||
| constexpr size_t alignments[] { alignof (std::max_align_t), | |||
| alignof (void*), | |||
| alignof (float), | |||
| alignof (double), | |||
| alignof (long double), | |||
| alignof (short int), | |||
| alignof (int), | |||
| alignof (long int), | |||
| alignof (long long int), | |||
| alignof (bool), | |||
| alignof (char), | |||
| alignof (char16_t), | |||
| alignof (char32_t), | |||
| alignof (wchar_t) }; | |||
| size_t max = 0; | |||
| for (const auto elem : alignments) | |||
| max = jmax (max, elem); | |||
| return max; | |||
| } | |||
| int numChannels = 0, size = 0; | |||
| size_t allocatedBytes = 0; | |||
| Type** channels; | |||
| HeapBlock<char, true> allocatedData; | |||
| Type* preallocatedChannelSpace[32]; | |||
| bool isClear = false; | |||
| static constexpr size_t maxAlignment = getMaxAlignment(); | |||
| JUCE_LEAK_DETECTOR (AudioBuffer) | |||
| }; | |||
| @@ -31,8 +31,6 @@ | |||
| #include "juce_audio_basics.h" | |||
| #include <juce_core/containers/juce_Optional.h> | |||
| #if JUCE_MINGW && ! defined (alloca) | |||
| #define alloca __builtin_alloca | |||
| #endif | |||
| @@ -55,46 +53,46 @@ | |||
| #include <arm_neon.h> | |||
| #endif | |||
| // #include "buffers/juce_AudioDataConverters.cpp" | |||
| #include "buffers/juce_AudioDataConverters.cpp" | |||
| #include "buffers/juce_FloatVectorOperations.cpp" | |||
| #include "buffers/juce_AudioChannelSet.cpp" | |||
| #include "buffers/juce_AudioProcessLoadMeasurer.cpp" | |||
| // #include "utilities/juce_IIRFilter.cpp" | |||
| // #include "utilities/juce_LagrangeInterpolator.cpp" | |||
| // #include "utilities/juce_WindowedSincInterpolator.cpp" | |||
| // #include "utilities/juce_Interpolators.cpp" | |||
| // #include "utilities/juce_SmoothedValue.cpp" | |||
| #include "utilities/juce_IIRFilter.cpp" | |||
| #include "utilities/juce_LagrangeInterpolator.cpp" | |||
| #include "utilities/juce_WindowedSincInterpolator.cpp" | |||
| #include "utilities/juce_Interpolators.cpp" | |||
| #include "utilities/juce_SmoothedValue.cpp" | |||
| #include "midi/juce_MidiBuffer.cpp" | |||
| // #include "midi/juce_MidiFile.cpp" | |||
| // #include "midi/juce_MidiKeyboardState.cpp" | |||
| #include "midi/juce_MidiFile.cpp" | |||
| #include "midi/juce_MidiKeyboardState.cpp" | |||
| #include "midi/juce_MidiMessage.cpp" | |||
| // #include "midi/juce_MidiMessageSequence.cpp" | |||
| // #include "midi/juce_MidiRPN.cpp" | |||
| // #include "mpe/juce_MPEValue.cpp" | |||
| // #include "mpe/juce_MPENote.cpp" | |||
| // #include "mpe/juce_MPEZoneLayout.cpp" | |||
| // #include "mpe/juce_MPEInstrument.cpp" | |||
| // #include "mpe/juce_MPEMessages.cpp" | |||
| // #include "mpe/juce_MPESynthesiserBase.cpp" | |||
| // #include "mpe/juce_MPESynthesiserVoice.cpp" | |||
| // #include "mpe/juce_MPESynthesiser.cpp" | |||
| // #include "mpe/juce_MPEUtils.cpp" | |||
| // #include "sources/juce_BufferingAudioSource.cpp" | |||
| // #include "sources/juce_ChannelRemappingAudioSource.cpp" | |||
| // #include "sources/juce_IIRFilterAudioSource.cpp" | |||
| // #include "sources/juce_MemoryAudioSource.cpp" | |||
| // #include "sources/juce_MixerAudioSource.cpp" | |||
| // #include "sources/juce_ResamplingAudioSource.cpp" | |||
| // #include "sources/juce_ReverbAudioSource.cpp" | |||
| // #include "sources/juce_ToneGeneratorAudioSource.cpp" | |||
| // #include "synthesisers/juce_Synthesiser.cpp" | |||
| #include "midi/juce_MidiMessageSequence.cpp" | |||
| #include "midi/juce_MidiRPN.cpp" | |||
| #include "mpe/juce_MPEValue.cpp" | |||
| #include "mpe/juce_MPENote.cpp" | |||
| #include "mpe/juce_MPEZoneLayout.cpp" | |||
| #include "mpe/juce_MPEInstrument.cpp" | |||
| #include "mpe/juce_MPEMessages.cpp" | |||
| #include "mpe/juce_MPESynthesiserBase.cpp" | |||
| #include "mpe/juce_MPESynthesiserVoice.cpp" | |||
| #include "mpe/juce_MPESynthesiser.cpp" | |||
| #include "mpe/juce_MPEUtils.cpp" | |||
| #include "sources/juce_BufferingAudioSource.cpp" | |||
| #include "sources/juce_ChannelRemappingAudioSource.cpp" | |||
| #include "sources/juce_IIRFilterAudioSource.cpp" | |||
| #include "sources/juce_MemoryAudioSource.cpp" | |||
| #include "sources/juce_MixerAudioSource.cpp" | |||
| #include "sources/juce_ResamplingAudioSource.cpp" | |||
| #include "sources/juce_ReverbAudioSource.cpp" | |||
| #include "sources/juce_ToneGeneratorAudioSource.cpp" | |||
| #include "synthesisers/juce_Synthesiser.cpp" | |||
| #include "midi/ump/juce_UMP.h" | |||
| #include "midi/ump/juce_UMPUtils.cpp" | |||
| #include "midi/ump/juce_UMPView.cpp" | |||
| #include "midi/ump/juce_UMPSysEx7.cpp" | |||
| #include "midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp" | |||
| #if JUCE_UNIT_TESTS | |||
| #include "utilities/juce_ADSR_test.cpp" | |||
| #include "midi/ump/juce_UMP_test.cpp" | |||
| @@ -32,7 +32,7 @@ | |||
| ID: juce_audio_basics | |||
| vendor: juce | |||
| version: 6.1.6 | |||
| version: 7.0.1 | |||
| name: JUCE audio and MIDI data classes | |||
| description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | |||
| website: http://www.juce.com/juce | |||
| @@ -120,4 +120,4 @@ | |||
| #include "sources/juce_ReverbAudioSource.h" | |||
| #include "sources/juce_ToneGeneratorAudioSource.h" | |||
| #include "synthesisers/juce_Synthesiser.h" | |||
| #include "audio_play_head/juce_AudioPlayHead.h" | |||
| #include "audio_play_head/juce_AudioPlayHead.h" | |||
| @@ -91,9 +91,9 @@ void BufferingAudioSource::releaseResources() | |||
| buffer.setSize (numberOfChannels, 0); | |||
| // MSVC2015 seems to need this if statement to not generate a warning during linking. | |||
| // MSVC2017 seems to need this if statement to not generate a warning during linking. | |||
| // As source is set in the constructor, there is no way that source could | |||
| // ever equal this, but it seems to make MSVC2015 happy. | |||
| // ever equal this, but it seems to make MSVC2017 happy. | |||
| if (source != this) | |||
| source->releaseResources(); | |||
| } | |||
| @@ -238,5 +238,5 @@ namespace juce | |||
| #include "audio_io/juce_AudioIODeviceType.cpp" | |||
| #include "midi_io/juce_MidiMessageCollector.cpp" | |||
| #include "midi_io/juce_MidiDevices.cpp" | |||
| // #include "sources/juce_AudioSourcePlayer.cpp" | |||
| // #include "sources/juce_AudioTransportSource.cpp" | |||
| #include "sources/juce_AudioSourcePlayer.cpp" | |||
| #include "sources/juce_AudioTransportSource.cpp" | |||
| @@ -32,7 +32,7 @@ | |||
| ID: juce_audio_devices | |||
| vendor: juce | |||
| version: 6.1.6 | |||
| version: 7.0.1 | |||
| name: JUCE audio and MIDI I/O device classes | |||
| description: Classes to play and record from audio and MIDI I/O devices | |||
| website: http://www.juce.com/juce | |||
| @@ -1,470 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ | |||
| STATICMETHOD (getNativeOutputSampleRate, "getNativeOutputSampleRate", "(I)I") \ | |||
| METHOD (constructor, "<init>", "(IIIIII)V") \ | |||
| METHOD (getState, "getState", "()I") \ | |||
| METHOD (play, "play", "()V") \ | |||
| METHOD (stop, "stop", "()V") \ | |||
| METHOD (release, "release", "()V") \ | |||
| METHOD (flush, "flush", "()V") \ | |||
| METHOD (write, "write", "([SII)I") \ | |||
| DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack") | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ | |||
| METHOD (constructor, "<init>", "(IIIII)V") \ | |||
| METHOD (getState, "getState", "()I") \ | |||
| METHOD (startRecording, "startRecording", "()V") \ | |||
| METHOD (stop, "stop", "()V") \ | |||
| METHOD (read, "read", "([SII)I") \ | |||
| METHOD (release, "release", "()V") \ | |||
| DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord") | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| enum | |||
| { | |||
| CHANNEL_OUT_STEREO = 12, | |||
| CHANNEL_IN_STEREO = 12, | |||
| CHANNEL_IN_MONO = 16, | |||
| ENCODING_PCM_16BIT = 2, | |||
| STREAM_MUSIC = 3, | |||
| MODE_STREAM = 1, | |||
| STATE_UNINITIALIZED = 0 | |||
| }; | |||
| const char* const javaAudioTypeName = "Android Audio"; | |||
| //============================================================================== | |||
| class AndroidAudioIODevice : public AudioIODevice, | |||
| public Thread | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| AndroidAudioIODevice (const String& deviceName) | |||
| : AudioIODevice (deviceName, javaAudioTypeName), | |||
| Thread ("audio"), | |||
| minBufferSizeOut (0), minBufferSizeIn (0), callback (nullptr), sampleRate (0), | |||
| numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2), | |||
| numClientOutputChannels (0), numDeviceOutputChannels (0), | |||
| actualBufferSize (0), isRunning (false), | |||
| inputChannelBuffer (1, 1), | |||
| outputChannelBuffer (1, 1) | |||
| { | |||
| JNIEnv* env = getEnv(); | |||
| sampleRate = env->CallStaticIntMethod (AudioTrack, AudioTrack.getNativeOutputSampleRate, MODE_STREAM); | |||
| minBufferSizeOut = (int) env->CallStaticIntMethod (AudioTrack, AudioTrack.getMinBufferSize, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT); | |||
| minBufferSizeIn = (int) env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_STEREO, ENCODING_PCM_16BIT); | |||
| if (minBufferSizeIn <= 0) | |||
| { | |||
| minBufferSizeIn = env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT); | |||
| if (minBufferSizeIn > 0) | |||
| numDeviceInputChannelsAvailable = 1; | |||
| else | |||
| numDeviceInputChannelsAvailable = 0; | |||
| } | |||
| DBG ("Audio device - min buffers: " << minBufferSizeOut << ", " << minBufferSizeIn << "; " | |||
| << sampleRate << " Hz; input chans: " << numDeviceInputChannelsAvailable); | |||
| } | |||
| ~AndroidAudioIODevice() override | |||
| { | |||
| close(); | |||
| } | |||
| StringArray getOutputChannelNames() override | |||
| { | |||
| StringArray s; | |||
| s.add ("Left"); | |||
| s.add ("Right"); | |||
| return s; | |||
| } | |||
| StringArray getInputChannelNames() override | |||
| { | |||
| StringArray s; | |||
| if (numDeviceInputChannelsAvailable == 2) | |||
| { | |||
| s.add ("Left"); | |||
| s.add ("Right"); | |||
| } | |||
| else if (numDeviceInputChannelsAvailable == 1) | |||
| { | |||
| s.add ("Audio Input"); | |||
| } | |||
| return s; | |||
| } | |||
| Array<double> getAvailableSampleRates() override | |||
| { | |||
| Array<double> r; | |||
| r.add ((double) sampleRate); | |||
| return r; | |||
| } | |||
| Array<int> getAvailableBufferSizes() override | |||
| { | |||
| Array<int> b; | |||
| int n = 16; | |||
| for (int i = 0; i < 50; ++i) | |||
| { | |||
| b.add (n); | |||
| n += n < 64 ? 16 | |||
| : (n < 512 ? 32 | |||
| : (n < 1024 ? 64 | |||
| : (n < 2048 ? 128 : 256))); | |||
| } | |||
| return b; | |||
| } | |||
| int getDefaultBufferSize() override { return 2048; } | |||
| String open (const BigInteger& inputChannels, | |||
| const BigInteger& outputChannels, | |||
| double requestedSampleRate, | |||
| int bufferSize) override | |||
| { | |||
| close(); | |||
| if (sampleRate != (int) requestedSampleRate) | |||
| return "Sample rate not allowed"; | |||
| lastError.clear(); | |||
| int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; | |||
| numDeviceInputChannels = 0; | |||
| numDeviceOutputChannels = 0; | |||
| activeOutputChans = outputChannels; | |||
| activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); | |||
| numClientOutputChannels = activeOutputChans.countNumberOfSetBits(); | |||
| activeInputChans = inputChannels; | |||
| activeInputChans.setRange (2, activeInputChans.getHighestBit(), false); | |||
| numClientInputChannels = activeInputChans.countNumberOfSetBits(); | |||
| actualBufferSize = preferredBufferSize; | |||
| inputChannelBuffer.setSize (2, actualBufferSize); | |||
| inputChannelBuffer.clear(); | |||
| outputChannelBuffer.setSize (2, actualBufferSize); | |||
| outputChannelBuffer.clear(); | |||
| JNIEnv* env = getEnv(); | |||
| if (numClientOutputChannels > 0) | |||
| { | |||
| numDeviceOutputChannels = 2; | |||
| outputDevice = GlobalRef (LocalRef<jobject>(env->NewObject (AudioTrack, AudioTrack.constructor, | |||
| STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, | |||
| (jint) (minBufferSizeOut * numDeviceOutputChannels * static_cast<int> (sizeof (int16))), MODE_STREAM))); | |||
| const bool supportsUnderrunCount = (getAndroidSDKVersion() >= 24); | |||
| getUnderrunCount = supportsUnderrunCount ? env->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : nullptr; | |||
| int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState); | |||
| if (outputDeviceState > 0) | |||
| { | |||
| isRunning = true; | |||
| } | |||
| else | |||
| { | |||
| // failed to open the device | |||
| outputDevice.clear(); | |||
| lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState); | |||
| } | |||
| } | |||
| if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0) | |||
| { | |||
| if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)) | |||
| { | |||
| // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio | |||
| // before trying to open an audio input device. This is not going to work! | |||
| jassertfalse; | |||
| inputDevice.clear(); | |||
| lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO"; | |||
| } | |||
| else | |||
| { | |||
| numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); | |||
| inputDevice = GlobalRef (LocalRef<jobject>(env->NewObject (AudioRecord, AudioRecord.constructor, | |||
| 0 /* (default audio source) */, sampleRate, | |||
| numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, | |||
| ENCODING_PCM_16BIT, | |||
| (jint) (minBufferSizeIn * numDeviceInputChannels * static_cast<int> (sizeof (int16)))))); | |||
| int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState); | |||
| if (inputDeviceState > 0) | |||
| { | |||
| isRunning = true; | |||
| } | |||
| else | |||
| { | |||
| // failed to open the device | |||
| inputDevice.clear(); | |||
| lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState); | |||
| } | |||
| } | |||
| } | |||
| if (isRunning) | |||
| { | |||
| if (outputDevice != nullptr) | |||
| env->CallVoidMethod (outputDevice, AudioTrack.play); | |||
| if (inputDevice != nullptr) | |||
| env->CallVoidMethod (inputDevice, AudioRecord.startRecording); | |||
| startThread (8); | |||
| } | |||
| else | |||
| { | |||
| closeDevices(); | |||
| } | |||
| return lastError; | |||
| } | |||
| void close() override | |||
| { | |||
| if (isRunning) | |||
| { | |||
| stopThread (2000); | |||
| isRunning = false; | |||
| closeDevices(); | |||
| } | |||
| } | |||
| int getOutputLatencyInSamples() override { return (minBufferSizeOut * 3) / 4; } | |||
| int getInputLatencyInSamples() override { return (minBufferSizeIn * 3) / 4; } | |||
| bool isOpen() override { return isRunning; } | |||
| int getCurrentBufferSizeSamples() override { return actualBufferSize; } | |||
| int getCurrentBitDepth() override { return 16; } | |||
| double getCurrentSampleRate() override { return sampleRate; } | |||
| BigInteger getActiveOutputChannels() const override { return activeOutputChans; } | |||
| BigInteger getActiveInputChannels() const override { return activeInputChans; } | |||
| String getLastError() override { return lastError; } | |||
| bool isPlaying() override { return isRunning && callback != nullptr; } | |||
| int getXRunCount() const noexcept override | |||
| { | |||
| if (outputDevice != nullptr && getUnderrunCount != nullptr) | |||
| return getEnv()->CallIntMethod (outputDevice, getUnderrunCount); | |||
| return -1; | |||
| } | |||
| void start (AudioIODeviceCallback* newCallback) override | |||
| { | |||
| if (isRunning && callback != newCallback) | |||
| { | |||
| if (newCallback != nullptr) | |||
| newCallback->audioDeviceAboutToStart (this); | |||
| const ScopedLock sl (callbackLock); | |||
| callback = newCallback; | |||
| } | |||
| } | |||
| void stop() override | |||
| { | |||
| if (isRunning) | |||
| { | |||
| AudioIODeviceCallback* lastCallback; | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| lastCallback = callback; | |||
| callback = nullptr; | |||
| } | |||
| if (lastCallback != nullptr) | |||
| lastCallback->audioDeviceStopped(); | |||
| } | |||
| } | |||
| void run() override | |||
| { | |||
| JNIEnv* env = getEnv(); | |||
| jshortArray audioBuffer = env->NewShortArray (actualBufferSize * jmax (numDeviceOutputChannels, numDeviceInputChannels)); | |||
| using NativeInt16 = AudioData::Format<AudioData::Int16, AudioData::NativeEndian>; | |||
| using NativeFloat32 = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>; | |||
| while (! threadShouldExit()) | |||
| { | |||
| if (inputDevice != nullptr) | |||
| { | |||
| jint numRead = env->CallIntMethod (inputDevice, AudioRecord.read, audioBuffer, 0, actualBufferSize * numDeviceInputChannels); | |||
| if (numRead < actualBufferSize * numDeviceInputChannels) | |||
| { | |||
| DBG ("Audio read under-run! " << numRead); | |||
| } | |||
| jshort* const src = env->GetShortArrayElements (audioBuffer, nullptr); | |||
| AudioData::deinterleaveSamples (AudioData::InterleavedSource<NativeInt16> { reinterpret_cast<const uint16*> (src), numDeviceInputChannels }, | |||
| AudioData::NonInterleavedDest<NativeFloat32> { inputChannelBuffer.getArrayOfWritePointers(), inputChannelBuffer.getNumChannels() }, | |||
| actualBufferSize); | |||
| env->ReleaseShortArrayElements (audioBuffer, src, 0); | |||
| } | |||
| if (threadShouldExit()) | |||
| break; | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| if (callback != nullptr) | |||
| { | |||
| callback->audioDeviceIOCallbackWithContext (inputChannelBuffer.getArrayOfReadPointers(), | |||
| numClientInputChannels, | |||
| outputChannelBuffer.getArrayOfWritePointers(), | |||
| numClientOutputChannels, | |||
| actualBufferSize, {}); | |||
| } | |||
| else | |||
| { | |||
| outputChannelBuffer.clear(); | |||
| } | |||
| } | |||
| if (outputDevice != nullptr) | |||
| { | |||
| if (threadShouldExit()) | |||
| break; | |||
| jshort* const dest = env->GetShortArrayElements (audioBuffer, nullptr); | |||
| AudioData::interleaveSamples (AudioData::NonInterleavedSource<NativeFloat32> { outputChannelBuffer.getArrayOfReadPointers(), outputChannelBuffer.getNumChannels() }, | |||
| AudioData::InterleavedDest<NativeInt16> { reinterpret_cast<uint16*> (dest), numDeviceOutputChannels }, | |||
| actualBufferSize); | |||
| env->ReleaseShortArrayElements (audioBuffer, dest, 0); | |||
| jint numWritten = env->CallIntMethod (outputDevice, AudioTrack.write, audioBuffer, 0, actualBufferSize * numDeviceOutputChannels); | |||
| if (numWritten < actualBufferSize * numDeviceOutputChannels) | |||
| { | |||
| DBG ("Audio write underrun! " << numWritten); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| int minBufferSizeOut, minBufferSizeIn; | |||
| private: | |||
| //============================================================================== | |||
| CriticalSection callbackLock; | |||
| AudioIODeviceCallback* callback; | |||
| jint sampleRate; | |||
| int numClientInputChannels, numDeviceInputChannels, numDeviceInputChannelsAvailable; | |||
| int numClientOutputChannels, numDeviceOutputChannels; | |||
| int actualBufferSize; | |||
| bool isRunning; | |||
| String lastError; | |||
| BigInteger activeOutputChans, activeInputChans; | |||
| GlobalRef outputDevice, inputDevice; | |||
| AudioBuffer<float> inputChannelBuffer, outputChannelBuffer; | |||
| jmethodID getUnderrunCount = nullptr; | |||
| void closeDevices() | |||
| { | |||
| if (outputDevice != nullptr) | |||
| { | |||
| outputDevice.callVoidMethod (AudioTrack.stop); | |||
| outputDevice.callVoidMethod (AudioTrack.release); | |||
| outputDevice.clear(); | |||
| } | |||
| if (inputDevice != nullptr) | |||
| { | |||
| inputDevice.callVoidMethod (AudioRecord.stop); | |||
| inputDevice.callVoidMethod (AudioRecord.release); | |||
| inputDevice.clear(); | |||
| } | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE (AndroidAudioIODevice) | |||
| }; | |||
| //============================================================================== | |||
| class AndroidAudioIODeviceType : public AudioIODeviceType | |||
| { | |||
| public: | |||
| AndroidAudioIODeviceType() : AudioIODeviceType (javaAudioTypeName) {} | |||
| //============================================================================== | |||
| void scanForDevices() {} | |||
| StringArray getDeviceNames (bool) const { return StringArray (javaAudioTypeName); } | |||
| int getDefaultDeviceIndex (bool) const { return 0; } | |||
| int getIndexOfDevice (AudioIODevice* device, bool) const { return device != nullptr ? 0 : -1; } | |||
| bool hasSeparateInputsAndOutputs() const { return false; } | |||
| AudioIODevice* createDevice (const String& outputDeviceName, | |||
| const String& inputDeviceName) | |||
| { | |||
| std::unique_ptr<AndroidAudioIODevice> dev; | |||
| if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) | |||
| { | |||
| dev.reset (new AndroidAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName | |||
| : inputDeviceName)); | |||
| if (dev->getCurrentSampleRate() <= 0 || dev->getDefaultBufferSize() <= 0) | |||
| dev = nullptr; | |||
| } | |||
| return dev.release(); | |||
| } | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType) | |||
| }; | |||
| //============================================================================== | |||
| extern bool isOboeAvailable(); | |||
| extern bool isOpenSLAvailable(); | |||
| } // namespace juce | |||
| @@ -1,131 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Some shared helpers methods for using the high-performance audio paths on | |||
| Android devices (OpenSL and Oboe). | |||
| @tags{Audio} | |||
| */ | |||
| namespace AndroidHighPerformanceAudioHelpers | |||
| { | |||
| //============================================================================== | |||
| static double getNativeSampleRate() | |||
| { | |||
| return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue(); | |||
| } | |||
| static int getNativeBufferSizeHint() | |||
| { | |||
| // This property is a hint of a native buffer size but it does not guarantee the size used. | |||
| auto deviceBufferSize = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue(); | |||
| if (deviceBufferSize == 0) | |||
| return 192; | |||
| return deviceBufferSize; | |||
| } | |||
| static bool isProAudioDevice() | |||
| { | |||
| static bool isSapaSupported = SystemStats::getDeviceManufacturer().containsIgnoreCase ("SAMSUNG") | |||
| && DynamicLibrary().open ("libapa_jni.so"); | |||
| return androidHasSystemFeature ("android.hardware.audio.pro") || isSapaSupported; | |||
| } | |||
| static bool hasLowLatencyAudioPath() | |||
| { | |||
| return androidHasSystemFeature ("android.hardware.audio.low_latency"); | |||
| } | |||
| static bool canUseHighPerformanceAudioPath (int nativeBufferSize, int requestedBufferSize, int requestedSampleRate) | |||
| { | |||
| return ((requestedBufferSize % nativeBufferSize) == 0) | |||
| && (requestedSampleRate == getNativeSampleRate()) | |||
| && isProAudioDevice(); | |||
| } | |||
| //============================================================================== | |||
| static int getMinimumBuffersToEnqueue (int nativeBufferSize, double requestedSampleRate) | |||
| { | |||
| if (canUseHighPerformanceAudioPath (nativeBufferSize, nativeBufferSize, (int) requestedSampleRate)) | |||
| { | |||
| // see https://developer.android.com/ndk/guides/audio/opensl/opensl-prog-notes.html#sandp | |||
| // "For Android 4.2 (API level 17) and earlier, a buffer count of two or more is required | |||
| // for lower latency. Beginning with Android 4.3 (API level 18), a buffer count of one | |||
| // is sufficient for lower latency." | |||
| return (getAndroidSDKVersion() >= 18 ? 1 : 2); | |||
| } | |||
| // not using low-latency path so we can use the absolute minimum number of buffers to queue | |||
| return 1; | |||
| } | |||
| static int buffersToQueueForBufferDuration (int nativeBufferSize, int bufferDurationInMs, double sampleRate) noexcept | |||
| { | |||
| auto maxBufferFrames = static_cast<int> (std::ceil (bufferDurationInMs * sampleRate / 1000.0)); | |||
| auto maxNumBuffers = static_cast<int> (std::ceil (static_cast<double> (maxBufferFrames) | |||
| / static_cast<double> (nativeBufferSize))); | |||
| return jmax (getMinimumBuffersToEnqueue (nativeBufferSize, sampleRate), maxNumBuffers); | |||
| } | |||
| static int getMaximumBuffersToEnqueue (int nativeBufferSize, double maximumSampleRate) noexcept | |||
| { | |||
| static constexpr int maxBufferSizeMs = 200; | |||
| return jmax (8, buffersToQueueForBufferDuration (nativeBufferSize, maxBufferSizeMs, maximumSampleRate)); | |||
| } | |||
| static Array<int> getAvailableBufferSizes (int nativeBufferSize, Array<double> availableSampleRates) | |||
| { | |||
| auto minBuffersToQueue = getMinimumBuffersToEnqueue (nativeBufferSize, getNativeSampleRate()); | |||
| auto maxBuffersToQueue = getMaximumBuffersToEnqueue (nativeBufferSize, findMaximum (availableSampleRates.getRawDataPointer(), | |||
| availableSampleRates.size())); | |||
| Array<int> bufferSizes; | |||
| for (int i = minBuffersToQueue; i <= maxBuffersToQueue; ++i) | |||
| bufferSizes.add (i * nativeBufferSize); | |||
| return bufferSizes; | |||
| } | |||
| static int getDefaultBufferSize (int nativeBufferSize, double currentSampleRate) | |||
| { | |||
| static constexpr int defaultBufferSizeForLowLatencyDeviceMs = 40; | |||
| static constexpr int defaultBufferSizeForStandardLatencyDeviceMs = 100; | |||
| auto defaultBufferLength = (hasLowLatencyAudioPath() ? defaultBufferSizeForLowLatencyDeviceMs | |||
| : defaultBufferSizeForStandardLatencyDeviceMs); | |||
| auto defaultBuffersToEnqueue = buffersToQueueForBufferDuration (nativeBufferSize, defaultBufferLength, currentSampleRate); | |||
| return defaultBuffersToEnqueue * nativeBufferSize; | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -1,701 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| // This byte-code is generated from native/java/com/rmsl/juce/JuceMidiSupport.java with min sdk version 23 | |||
| // See juce_core/native/java/README.txt on how to generate this byte-code. | |||
| static const uint8 javaMidiByteCode[] = | |||
| {31,139,8,8,43,113,161,94,0,3,106,97,118,97,77,105,100,105,66,121,116,101,67,111,100,101,46,100,101,120,0,149,124,11,124,220, | |||
| 69,181,255,153,223,99,119,179,217,36,155,77,218,164,105,178,217,164,73,179,165,205,171,233,35,109,146,182,121,180,77,218,164,45, | |||
| 201,182,72,195,5,183,201,182,217,146,236,134,236,166,180,114,189,20,244,210,162,168,40,80,65,177,162,2,242,18,81,65,80,17,81, | |||
| 80,81,81,122,149,63,214,39,138,112,69,69,64,20,17,229,218,255,247,204,204,110,126,109,3,213,246,243,221,51,191,51,103,206,204, | |||
| 156,57,115,230,204,111,183,29,141,237,247,54,181,44,167,234,195,55,252,236,231,159,31,184,160,244,71,71,143,188,248,212,248, | |||
| 199,142,188,62,189,243,225,179,11,151,53,157,77,52,73,68,251,119,44,11,144,254,243,246,109,68,231,10,197,95,15,60,105,19,157,3, | |||
| 250,188,139,40,4,250,134,151,232,179,76,115,137,114,64,211,133,68,55,174,33,186,22,26,254,86,79,244,119,224,255,0,163,129,200, | |||
| 6,22,3,13,64,43,176,14,232,1,54,1,219,128,93,192,81,224,105,224,31,192,63,1,163,145,200,13,132,129,173,192,32,240,54,224,66, | |||
| 224,82,224,125,192,167,128,91,129,59,128,207,2,247,2,95,6,30,2,30,1,190,3,188,4,20,55,17,173,4,118,1,215,0,15,3,127,0,252,205, | |||
| 68,109,192,249,192,101,192,221,192,143,128,23,129,130,165,68,29,192,46,224,74,224,51,192,47,129,146,22,162,85,192,249,192,101, | |||
| 192,17,224,46,224,155,192,79,128,63,2,198,50,216,14,120,47,240,16,240,10,16,90,78,148,0,238,5,126,11,204,89,65,180,2,216,9,188, | |||
| 3,248,24,240,32,112,28,120,9,48,86,162,47,96,49,176,22,216,1,164,129,107,128,219,129,135,1,187,149,168,9,232,1,222,6,76,0,151, | |||
| 1,71,128,187,128,175,2,79,0,214,42,244,7,132,129,181,192,32,112,29,112,59,112,63,240,75,224,215,192,115,192,239,129,151,129,191, | |||
| 1,111,0,98,53,214,1,200,7,138,128,82,32,8,212,0,139,129,165,192,10,96,21,208,1,116,2,235,129,56,112,61,240,16,240,35,224,121, | |||
| 224,85,64,180,17,121,129,2,160,20,88,8,180,0,107,129,141,192,185,192,52,112,37,240,81,224,115,192,35,192,15,128,227,192,239, | |||
| 129,87,128,215,1,119,59,244,0,149,192,66,160,30,88,1,116,3,3,192,78,96,4,184,8,216,15,92,2,28,2,62,0,220,0,124,10,248,60,240,53, | |||
| 224,7,192,79,129,231,128,63,1,175,3,86,7,244,3,107,129,62,96,28,120,31,112,45,240,73,224,110,224,94,224,107,192,163,192,49,224, | |||
| 23,192,235,192,92,236,133,122,160,11,56,15,72,1,239,4,174,6,62,10,220,9,220,15,60,12,252,1,120,5,120,29,48,214,98,46,192,86, | |||
| 224,0,112,45,112,23,112,31,240,13,224,127,128,223,0,127,4,222,0,242,214,97,254,64,24,88,9,172,3,54,1,219,128,97,224,124,96,4, | |||
| 136,3,147,192,37,192,97,224,58,224,40,240,105,224,94,224,33,224,49,224,41,224,103,192,111,128,151,128,191,1,255,4,188,157,68, | |||
| 21,64,61,176,26,88,3,116,3,189,192,22,96,59,112,1,16,3,246,2,83,192,21,192,199,129,251,128,199,129,231,128,215,0,111,23,209,60, | |||
| 96,17,176,14,56,27,24,3,46,2,46,5,62,8,220,9,124,9,248,54,240,4,240,28,240,119,192,213,13,95,6,26,128,94,224,60,96,28,184,24, | |||
| 184,12,184,10,184,6,248,56,112,43,240,21,224,199,192,203,192,95,129,55,0,119,15,81,33,48,31,88,12,44,7,122,128,45,192,249,64, | |||
| 12,216,11,92,14,188,7,56,2,124,10,120,8,248,22,240,4,240,99,224,23,192,51,192,171,128,23,65,178,8,168,0,106,129,197,192,70,224, | |||
| 108,96,23,144,4,46,5,174,2,62,4,220,0,220,12,124,1,248,10,240,117,224,123,192,83,192,207,129,103,129,151,128,191,1,198,6,216, | |||
| 11,88,6,108,1,182,3,5,136,185,197,64,53,176,0,168,1,106,129,133,64,29,16,6,22,1,103,1,139,129,37,0,194,49,33,180,18,66,34,33, | |||
| 252,17,194,28,33,164,17,66,22,33,68,17,194,18,33,244,16,66,11,33,108,16,182,63,97,203,18,182,26,97,59,16,220,154,224,114,132, | |||
| 37,36,44,5,193,148,212,163,207,7,12,137,54,2,189,64,31,176,9,216,12,244,3,3,192,22,96,43,128,99,133,112,220,208,32,48,4,68,128, | |||
| 29,192,219,128,97,224,63,128,11,248,252,1,118,1,163,64,12,216,13,140,3,255,9,92,10,28,4,46,3,46,7,222,5,188,155,148,77,50,127, | |||
| 252,154,78,98,226,133,186,188,31,229,50,80,67,63,115,217,212,229,74,93,158,212,50,150,230,87,233,242,65,205,247,56,228,113,4, | |||
| 210,85,154,159,171,249,243,129,60,224,90,205,207,119,244,85,224,40,7,28,242,197,90,158,203,165,142,182,101,142,190,202,245,216, | |||
| 88,38,168,101,42,117,121,82,151,25,55,106,153,106,45,83,161,203,55,47,81,178,92,190,75,203,215,56,218,214,234,182,220,15,251, | |||
| 208,3,122,12,13,142,113,54,58,198,214,228,24,27,151,31,94,162,242,2,46,63,182,100,134,159,177,103,179,67,79,179,99,252,92,62, | |||
| 230,40,103,230,184,204,209,87,171,163,47,246,201,227,154,191,90,243,217,47,58,116,121,66,151,185,109,66,151,127,133,114,82,151, | |||
| 159,95,162,114,26,46,255,5,229,139,116,217,194,230,216,175,203,62,148,167,116,185,20,229,148,46,135,80,222,167,203,75,80,190, | |||
| 88,151,151,57,202,235,234,103,116,246,59,202,55,58,250,138,56,248,231,57,250,29,117,240,39,29,229,253,142,126,15,58,248,135,29, | |||
| 109,175,70,249,64,166,47,135,252,109,40,191,67,151,239,113,180,61,230,24,15,175,93,70,254,49,7,127,210,81,126,208,209,215,163, | |||
| 40,79,103,244,160,124,137,46,31,119,216,234,87,40,167,117,249,133,122,181,111,215,232,53,122,167,46,243,26,253,151,46,179,253, | |||
| 51,229,135,29,252,140,255,116,234,182,92,238,114,248,67,183,195,31,122,52,127,190,46,95,43,125,190,137,238,39,69,215,10,110, | |||
| 83,64,87,201,182,205,244,1,73,87,210,135,36,245,80,135,96,31,46,165,247,242,90,163,247,231,37,21,244,71,73,107,169,74,214,47,164, | |||
| 197,130,227,66,177,148,171,210,252,42,205,95,160,159,153,110,19,188,199,44,250,48,49,245,211,95,36,85,245,53,186,190,86,143,167, | |||
| 22,145,247,136,164,93,116,167,164,37,244,138,164,203,232,53,93,95,46,20,13,10,181,71,111,39,166,107,232,247,164,227,190,224, | |||
| 216,95,73,31,228,50,36,95,37,142,117,30,122,84,82,147,190,37,169,77,63,37,142,117,110,250,184,164,213,244,85,77,159,228,117, | |||
| 192,137,241,49,77,63,43,169,69,223,150,116,11,45,135,126,27,124,55,113,28,236,165,62,193,116,5,13,8,190,3,40,190,55,75,189,116, | |||
| 189,164,57,180,30,245,62,173,39,79,215,231,129,115,189,164,185,212,45,20,237,17,28,35,243,232,235,196,180,138,126,70,28,199, | |||
| 213,120,252,136,164,63,144,180,128,74,4,83,63,205,23,28,219,213,184,57,198,63,165,233,207,73,197,215,239,75,58,72,199,37,45,164, | |||
| 159,104,62,215,23,107,189,197,56,165,214,65,207,28,61,174,18,156,74,223,145,180,137,230,8,166,171,105,174,164,29,212,44,105, | |||
| 59,237,16,28,167,85,251,82,216,255,168,166,108,175,121,90,79,25,198,255,32,113,60,13,208,151,136,227,176,65,183,72,63,92,47,235, | |||
| 217,239,20,21,244,136,164,181,244,61,73,183,211,15,37,221,72,66,250,235,98,42,148,116,9,5,36,61,155,106,36,221,68,155,36,221, | |||
| 64,219,165,95,174,147,250,66,122,92,76,239,149,84,217,39,132,72,254,11,73,7,232,15,186,62,79,182,235,167,34,73,55,83,151,80,252, | |||
| 94,77,251,165,95,175,149,122,171,180,222,42,173,183,74,235,173,210,250,170,116,251,42,221,190,74,183,175,214,237,170,181,124, | |||
| 181,150,175,214,242,213,90,190,90,203,47,192,78,231,254,22,32,43,49,228,243,50,50,53,181,36,93,74,182,164,203,201,165,169,91, | |||
| 243,243,53,45,144,180,153,252,154,22,203,253,214,37,245,214,160,255,143,72,90,77,223,144,212,69,223,37,117,22,62,46,233,89, | |||
| 180,90,238,51,181,62,181,122,190,181,240,148,251,36,157,71,95,148,116,33,61,36,169,90,191,90,248,205,99,146,238,160,39,36,221,78, | |||
| 199,52,253,31,73,139,232,71,146,214,208,255,147,116,62,253,88,210,85,228,145,253,181,82,142,166,94,161,248,185,146,182,145,79, | |||
| 168,120,80,42,233,92,154,39,105,41,149,73,186,149,170,37,109,164,5,146,118,83,139,164,27,40,34,227,68,189,156,199,66,100,94, | |||
| 247,232,56,241,180,140,15,103,97,230,138,186,37,157,67,95,147,180,140,30,38,62,235,23,75,126,163,150,135,118,26,18,76,43,232, | |||
| 109,130,207,118,213,174,73,219,167,9,158,254,77,226,51,92,245,211,12,59,255,142,56,183,236,145,114,45,240,124,222,15,203,116, | |||
| 187,101,144,59,172,159,111,212,207,55,73,90,71,47,232,231,165,66,229,1,27,37,141,208,160,224,28,53,76,239,35,206,83,149,158, | |||
| 21,186,253,10,200,127,66,210,74,217,207,10,100,191,47,75,26,162,38,161,248,172,111,165,110,183,82,247,191,82,247,179,82,247,179, | |||
| 82,247,211,138,241,255,146,152,6,233,159,196,121,135,26,215,106,77,219,180,158,54,100,187,107,4,231,199,234,185,93,251,23,159, | |||
| 77,96,203,119,35,36,227,2,206,50,36,226,55,32,17,62,178,69,229,97,194,53,147,71,113,253,213,168,127,98,139,122,14,233,246,204, | |||
| 127,251,18,69,111,66,253,31,116,125,149,174,111,114,212,63,128,250,186,173,170,126,129,214,107,59,244,31,67,253,144,174,175,209, | |||
| 252,118,71,253,175,80,255,30,93,95,171,245,207,1,198,180,254,151,81,255,57,93,191,80,183,115,142,127,29,228,22,109,83,207, | |||
| 117,142,241,101,234,183,161,190,91,215,115,14,30,197,197,96,108,64,201,165,52,189,124,96,166,238,26,71,249,227,186,254,14,7, | |||
| 239,11,186,252,16,232,55,29,229,99,3,42,151,103,153,159,1,255,171,219,254,73,83,99,139,162,69,154,134,53,237,208,52,162,105,108, | |||
| 203,76,95,251,53,239,93,91,88,183,33,203,231,111,80,247,140,73,127,30,158,171,225,59,147,254,79,226,121,216,111,33,234,15,251, | |||
| 13,26,14,24,56,183,88,158,245,36,55,168,123,66,4,53,23,249,175,32,62,21,19,161,113,172,181,87,222,13,44,45,183,111,131,186,67, | |||
| 92,36,123,241,137,68,200,192,126,130,172,223,150,207,124,30,152,168,99,217,119,109,80,103,94,36,100,81,164,202,130,204,45,168, | |||
| 241,138,5,184,224,38,66,183,98,124,62,248,98,143,148,177,101,22,128,188,17,109,230,130,78,249,111,71,159,62,49,229,255,52,183, | |||
| 49,90,141,60,240,110,67,153,219,248,40,16,72,52,173,130,39,133,95,206,215,35,35,58,186,65,217,129,239,53,46,57,51,220,179,55, | |||
| 168,123,99,160,112,105,177,77,129,170,150,226,66,140,163,16,253,249,176,127,114,41,210,204,227,226,91,148,207,72,132,62,5,223, | |||
| 13,116,182,20,87,34,126,205,161,50,227,66,186,40,212,12,222,76,139,192,41,45,110,150,181,150,182,69,59,34,105,190,156,11,247, | |||
| 253,205,13,234,94,227,180,85,39,180,32,122,106,253,95,208,250,3,162,64,68,154,149,229,133,148,252,79,105,169,240,171,94,104,98, | |||
| 237,79,109,80,239,56,3,37,1,151,214,7,61,94,42,179,160,199,206,147,122,34,232,59,33,47,151,62,177,74,100,234,124,186,46,252, | |||
| 74,107,206,114,170,54,188,240,4,182,89,153,101,161,191,38,182,178,149,8,249,113,6,85,155,121,168,11,192,114,137,80,49,178,101, | |||
| 230,207,193,45,215,103,5,106,185,20,161,5,233,117,232,161,0,173,125,246,128,109,185,46,242,127,68,181,247,23,161,149,207,78, | |||
| 172,203,165,206,255,14,127,53,17,242,225,6,28,254,18,101,253,203,187,81,221,73,79,246,175,75,225,95,249,200,211,92,202,231,55, | |||
| 170,123,232,164,191,1,109,134,23,228,208,112,141,139,134,107,189,180,115,161,7,150,63,47,228,150,107,107,75,255,18,84,183,81, | |||
| 197,146,128,25,233,116,81,171,112,19,211,132,127,49,234,34,157,57,224,228,72,26,233,242,162,175,255,130,157,135,187,161,179,219, | |||
| 5,45,121,122,5,2,122,5,194,207,171,120,196,186,133,168,199,241,37,228,152,218,209,7,199,206,132,159,51,254,164,255,42,237,95, | |||
| 25,31,239,217,168,226,104,36,132,126,170,184,159,41,233,215,133,50,142,8,233,151,155,54,170,189,26,128,229,132,230,109,219,56, | |||
| 227,171,249,152,63,223,221,119,108,84,123,171,35,215,71,67,151,121,200,125,208,125,141,184,89,220,103,125,107,159,167,83,203, | |||
| 90,250,246,191,219,209,222,208,99,153,218,168,98,98,196,159,163,60,213,15,171,64,98,187,223,45,125,133,159,19,161,37,24,95,192, | |||
| 127,158,223,125,82,219,75,222,162,109,107,182,109,61,183,165,76,91,30,11,143,225,221,122,109,39,253,124,235,24,22,62,26,54,114, | |||
| 105,24,222,148,159,93,171,107,29,107,149,171,215,42,23,86,93,32,215,202,167,215,202,135,181,202,203,174,21,244,116,231,254, | |||
| 27,107,117,123,118,173,250,102,93,171,207,101,215,10,253,84,229,205,186,86,247,103,214,10,158,232,214,51,252,42,120,69,220, | |||
| 174,89,143,28,52,177,174,134,58,99,51,99,235,20,122,108,175,169,119,50,122,108,222,204,122,63,233,88,175,12,239,167,14,158,41,71, | |||
| 137,115,110,163,122,143,51,44,10,96,79,182,234,176,145,47,227,180,122,27,244,130,163,77,198,23,254,58,11,207,232,117,198,66, | |||
| 75,206,41,191,87,197,227,64,168,197,46,128,15,36,224,179,38,172,193,81,195,195,251,23,113,231,157,242,54,51,163,167,162,247,116, | |||
| 221,139,102,225,45,159,133,215,217,123,242,252,248,207,192,44,188,115,28,60,91,90,14,231,90,47,71,8,182,67,17,236,240,25,105, | |||
| 7,156,91,102,33,13,91,60,66,75,90,209,164,169,94,245,30,168,220,168,161,10,35,32,134,155,3,88,249,187,81,3,159,109,246,99,189, | |||
| 10,36,77,192,103,133,46,113,180,97,73,63,158,11,33,225,149,52,225,47,213,252,66,10,26,184,235,137,160,81,39,242,68,248,53,158, | |||
| 205,60,212,85,202,241,153,50,255,112,73,159,90,116,105,253,146,69,186,108,208,7,122,85,78,90,110,98,44,102,100,16,186,141,5, | |||
| 196,52,225,159,199,49,83,36,252,213,242,52,11,44,107,233,154,11,110,21,71,123,163,204,186,6,190,216,136,8,204,103,155,137,253, | |||
| 230,201,158,48,65,179,16,40,195,242,133,223,200,67,169,206,80,249,195,66,180,172,215,190,36,112,255,202,248,239,103,122,85,125, | |||
| 196,239,207,250,53,215,124,161,55,115,190,171,209,200,248,235,87,111,10,213,249,174,246,233,151,122,249,30,137,57,8,204,65,68, | |||
| 154,2,24,77,30,69,154,10,249,14,64,252,28,89,10,13,161,125,136,220,65,193,254,31,20,117,164,250,44,146,125,21,99,223,168,179, | |||
| 252,209,94,245,14,53,96,77,250,107,97,185,225,72,49,13,239,40,134,69,138,169,204,252,51,180,148,224,70,228,51,42,141,18,26,30, | |||
| 44,1,191,132,86,225,124,42,51,176,163,204,49,185,219,113,115,194,153,181,2,62,240,81,62,19,6,231,225,105,25,158,62,36,159,74, | |||
| 79,170,43,59,233,105,142,212,151,240,47,100,203,83,149,25,48,150,47,245,210,6,76,51,17,74,35,214,29,55,12,172,173,148,105,90, | |||
| 68,131,86,248,113,143,62,171,254,212,171,114,207,200,72,41,218,31,225,157,97,113,30,98,145,215,108,53,155,100,30,98,201,113,7, | |||
| 41,130,160,86,105,250,165,77,205,153,21,54,3,203,91,134,126,123,66,175,176,89,102,171,21,30,202,174,240,79,78,232,21,54,19,161, | |||
| 247,227,172,101,205,79,156,40,52,2,70,248,159,46,61,142,185,125,234,253,120,164,171,12,250,63,206,243,48,166,252,119,104,250, | |||
| 105,80,110,53,95,142,199,144,154,27,41,210,13,217,208,81,57,150,74,172,97,194,63,34,71,192,189,12,73,249,103,79,20,138,128,8, | |||
| 255,147,227,140,242,158,230,62,245,174,189,220,174,165,10,59,146,230,89,95,207,179,181,22,116,117,241,89,144,86,118,192,156,93, | |||
| 66,102,100,46,212,181,90,115,101,207,46,104,175,52,131,116,28,241,46,130,43,81,165,165,172,193,121,193,128,101,8,33,194,191,13, | |||
| 218,133,70,158,21,180,235,44,246,139,6,217,107,163,244,19,254,59,214,167,222,209,151,91,232,31,190,50,193,185,150,57,60,52, | |||
| 151,130,214,204,106,39,66,23,82,140,103,162,86,196,228,113,152,114,28,65,57,14,53,227,133,50,170,207,145,185,203,53,216,81,9, | |||
| 255,229,188,30,214,69,254,43,117,166,195,220,240,51,121,102,208,170,51,121,158,108,197,5,77,156,145,222,192,86,196,92,144,145, | |||
| 154,60,78,142,24,43,179,123,108,5,185,244,110,186,70,175,75,185,129,241,26,145,78,101,29,49,51,38,209,42,74,51,99,194,170,4, | |||
| 229,109,183,82,40,187,200,252,206,95,41,243,187,150,179,159,63,17,52,56,222,4,40,252,127,42,226,168,189,84,39,123,10,19,231,157, | |||
| 220,239,93,125,234,59,136,114,47,250,244,182,250,139,41,48,47,209,148,162,27,243,124,88,211,45,20,249,250,60,204,224,99,200, | |||
| 197,185,119,55,86,45,232,45,164,192,162,240,139,131,205,243,105,50,116,17,238,192,62,119,171,123,21,69,246,97,44,46,68,70,87,11, | |||
| 164,90,225,175,131,205,21,104,59,31,121,183,15,121,118,14,110,11,33,114,127,221,122,118,159,139,223,144,182,98,6,172,189,218, | |||
| 106,129,158,171,97,197,68,211,77,212,108,5,189,225,99,24,177,183,78,168,246,101,220,222,211,234,249,221,137,106,156,131,147,235, | |||
| 122,233,225,214,240,51,65,47,102,246,32,201,251,124,55,102,196,223,189,168,44,104,163,244,63,94,255,122,48,249,253,98,192,21, | |||
| 73,97,55,134,206,194,126,80,190,16,73,205,129,205,110,228,83,42,165,246,128,90,249,59,100,182,201,214,182,165,79,151,72,107,219, | |||
| 210,3,26,149,44,246,64,145,92,77,222,3,187,32,31,254,157,90,243,128,57,140,250,32,116,207,59,85,163,99,151,151,57,118,249,66, | |||
| 146,178,208,184,64,106,108,65,187,15,202,118,149,102,53,228,182,177,246,223,12,239,155,7,185,217,34,70,117,70,151,242,5,83,80, | |||
| 43,34,6,211,74,211,146,39,147,233,120,114,201,167,76,84,41,101,221,63,83,122,175,69,153,179,26,206,190,56,143,89,4,251,45,211, | |||
| 249,141,192,109,182,64,223,21,70,54,169,247,211,51,123,39,242,212,92,170,218,19,48,197,242,229,211,45,216,107,46,59,225,15,113, | |||
| 244,241,84,93,9,238,178,229,215,214,32,50,122,144,199,7,101,244,106,249,205,92,170,246,46,204,156,16,185,129,162,150,53,240, | |||
| 166,162,132,191,130,235,125,147,235,14,210,151,31,225,189,244,73,58,110,90,66,44,11,63,29,48,195,127,58,110,218,66,44,15,255,160, | |||
| 208,176,245,222,190,114,147,218,43,129,80,185,192,138,194,243,63,192,86,49,86,25,33,120,83,28,62,193,62,135,155,153,63,200,39, | |||
| 245,210,57,210,139,187,228,141,207,141,179,37,252,87,117,194,76,134,246,106,89,139,185,127,8,138,128,204,3,121,190,243,49,38, | |||
| 158,77,142,180,67,69,54,167,61,186,73,249,93,192,63,25,74,200,155,99,97,182,238,230,76,93,104,166,46,160,199,124,199,38,245,253, | |||
| 158,7,188,237,158,114,106,245,108,193,189,75,237,60,15,242,163,72,46,172,249,96,192,35,174,92,254,157,77,176,91,110,78,192, | |||
| 208,59,218,195,109,6,243,230,83,203,241,85,196,54,134,230,188,170,95,6,60,203,159,111,166,13,86,158,135,45,140,249,187,90,246, | |||
| 177,117,43,164,135,200,54,5,21,212,242,231,114,240,202,217,83,108,182,19,124,27,251,41,63,147,39,120,202,114,254,33,79,145,235, | |||
| 81,95,105,55,243,14,183,3,117,225,123,143,123,60,34,124,236,184,39,71,136,43,195,247,7,189,101,72,142,195,127,206,243,96,111, | |||
| 122,56,131,28,68,235,183,103,227,216,5,217,187,241,156,205,234,59,56,142,172,221,50,42,201,40,230,184,115,23,56,238,220,11,101, | |||
| 44,133,55,24,45,131,47,159,96,107,241,25,98,105,159,59,107,179,186,187,241,249,199,185,87,160,185,197,111,194,79,35,56,227,3, | |||
| 34,177,46,76,77,129,240,235,252,78,219,144,57,212,50,200,223,196,241,44,15,86,205,75,139,121,188,91,61,108,69,15,172,19,112,7, | |||
| 114,248,198,23,241,149,225,182,248,30,62,99,242,217,47,94,128,239,173,242,157,47,123,129,156,47,176,166,229,133,38,142,132,176, | |||
| 144,135,124,190,178,124,117,206,190,32,45,132,115,214,86,183,115,68,6,75,233,83,254,213,143,182,173,190,118,154,225,29,5,207, | |||
| 231,174,116,91,14,222,39,192,171,206,173,145,28,15,252,205,99,32,23,61,39,74,185,85,167,141,13,17,241,133,220,234,252,54,236, | |||
| 183,59,49,235,214,156,38,42,242,29,115,95,246,176,237,231,156,17,59,121,221,93,244,144,63,152,151,159,25,143,143,117,240,14,120, | |||
| 152,124,222,86,47,188,234,46,65,238,135,43,97,77,55,226,180,71,198,5,183,140,7,110,74,227,60,41,162,96,94,248,215,121,190,96, | |||
| 94,157,175,200,55,74,225,31,230,249,194,175,241,89,49,141,21,226,239,215,216,155,142,100,115,185,235,197,165,245,71,197,245,130, | |||
| 243,62,67,230,218,15,108,86,223,195,149,187,97,115,55,159,69,94,156,232,108,115,156,231,70,228,136,154,143,193,235,0,27,93,129, | |||
| 117,104,117,33,154,30,65,236,9,93,71,183,226,121,149,171,150,78,150,59,10,57,159,171,210,197,81,118,84,230,2,39,215,127,2,245, | |||
| 172,161,218,19,164,201,166,165,116,155,201,81,227,10,10,186,243,73,143,192,226,213,227,220,162,204,163,86,239,10,25,199,177,122, | |||
| 162,92,90,75,200,92,85,82,23,91,44,142,213,109,181,225,91,67,42,102,182,154,30,29,69,85,244,228,168,233,165,240,19,121,174, | |||
| 240,247,243,92,65,119,157,75,157,169,156,87,76,234,120,121,177,220,15,136,82,151,214,167,247,209,62,105,35,142,13,158,126,245, | |||
| 61,127,57,102,90,33,109,227,37,175,205,231,231,185,242,253,207,64,54,54,71,4,122,23,22,5,138,3,115,90,144,248,4,172,200,117, | |||
| 234,68,177,229,217,116,135,166,242,140,194,138,190,120,66,159,81,242,68,25,172,155,79,90,187,167,229,208,139,39,100,91,88,179, | |||
| 86,122,174,58,97,108,89,86,39,12,34,100,113,248,241,86,225,209,55,25,117,139,137,92,199,107,243,97,212,6,93,200,197,237,160, | |||
| 171,206,86,115,61,87,238,245,157,217,119,99,155,251,79,190,15,242,253,121,168,63,27,31,183,93,66,75,35,62,153,109,24,178,238, | |||
| 63,250,213,61,148,223,197,5,140,201,193,75,168,219,207,245,57,114,191,27,20,235,87,191,169,8,20,113,236,227,93,195,107,196,223, | |||
| 90,114,70,102,82,129,161,78,112,126,135,232,134,5,91,109,68,115,43,252,42,238,48,86,157,17,153,154,67,186,149,29,112,241,238, | |||
| 25,116,69,166,138,193,155,43,179,219,192,156,106,215,2,248,203,14,218,231,78,172,195,9,24,243,33,171,193,138,59,218,201,86,34, | |||
| 104,137,165,225,239,242,189,245,16,133,255,174,222,121,122,49,83,254,158,117,177,180,65,105,54,70,189,191,95,189,31,200,196, | |||
| 164,58,196,36,117,15,85,86,250,176,182,71,196,63,79,238,126,126,55,165,252,194,162,155,250,213,111,63,120,142,94,185,51,56,186, | |||
| 169,157,20,57,162,162,202,173,146,143,123,204,17,21,81,110,149,107,13,255,52,212,170,25,114,213,12,93,255,9,212,243,169,118, | |||
| 187,244,230,0,234,98,236,41,50,91,177,179,187,140,178,187,136,61,31,178,176,46,124,175,251,164,91,135,43,17,186,24,187,66,101, | |||
| 11,65,119,248,94,229,245,121,162,204,133,155,148,167,72,222,164,174,192,153,160,222,211,244,192,42,91,244,253,251,108,29,47, | |||
| 206,209,249,174,65,67,151,214,239,24,202,190,219,249,75,191,243,221,206,185,98,62,157,103,148,211,185,102,133,124,107,165,110, | |||
| 157,214,128,122,39,31,16,171,16,115,11,112,142,188,79,102,78,76,57,147,111,89,250,218,9,247,54,62,65,6,187,42,104,16,109,91, | |||
| 150,190,120,98,123,87,57,109,55,203,81,126,254,196,96,215,124,240,113,106,46,125,230,68,160,48,252,116,230,29,31,206,168,1,245, | |||
| 125,68,53,238,11,131,157,243,233,135,129,131,160,21,84,100,30,164,229,205,69,178,124,79,213,100,232,16,199,88,255,97,121,62, | |||
| 109,239,196,153,141,157,18,248,211,231,171,10,68,145,184,148,194,47,65,235,223,213,13,25,121,211,128,246,123,216,172,18,8,98,46, | |||
| 153,119,72,77,3,42,167,80,243,205,215,177,212,160,229,3,250,253,149,152,121,251,106,82,161,208,239,91,113,86,254,227,68,185, | |||
| 81,135,251,192,46,17,164,85,2,89,180,168,196,154,181,32,35,143,131,19,148,252,240,243,153,28,191,80,234,16,250,187,18,33,51,24, | |||
| 83,247,181,117,64,125,71,83,70,234,142,108,200,222,44,249,61,113,57,114,161,10,177,11,245,171,136,51,245,90,244,177,7,109,121, | |||
| 38,65,201,15,191,152,185,147,231,233,62,42,178,125,148,203,223,192,10,34,202,216,130,231,122,83,64,125,199,116,27,232,61,217, | |||
| 95,217,170,63,15,234,231,140,60,255,198,234,49,240,142,157,194,223,164,249,63,63,165,253,243,167,60,255,37,160,250,204,252,134, | |||
| 137,223,57,90,69,234,183,69,197,69,234,187,152,242,34,245,155,9,31,232,152,214,27,7,173,193,243,133,160,75,138,212,111,63,150, | |||
| 129,182,23,157,172,191,247,148,103,65,250,251,44,82,177,140,41,255,150,199,144,180,89,198,202,2,100,203,182,174,43,119,204,73, | |||
| 144,202,105,12,253,100,106,186,134,102,126,167,149,249,30,201,144,180,73,62,47,212,252,238,172,156,79,183,85,191,50,81,125,173, | |||
| 205,182,103,152,89,204,149,177,203,208,54,50,29,182,82,60,151,230,185,36,79,149,221,89,93,57,154,250,53,13,104,153,128,214,43, | |||
| 223,179,211,204,247,103,114,143,201,12,90,217,158,101,249,251,228,133,186,95,254,46,119,161,222,139,181,248,107,105,234,215, | |||
| 241,97,149,110,179,74,223,73,88,174,93,239,165,53,186,110,173,30,191,149,45,203,89,135,201,12,247,225,14,179,136,106,154,90,186, | |||
| 90,155,214,47,239,172,95,223,179,190,181,126,89,87,75,75,125,231,202,229,205,245,43,186,215,183,44,91,223,189,172,123,101, | |||
| 19,76,139,124,173,125,100,60,158,136,167,215,144,171,93,81,99,77,27,89,107,218,22,237,224,79,148,253,93,227,211,177,116,50,153, | |||
| 30,27,136,38,162,123,98,83,180,250,84,78,40,54,53,149,156,90,29,26,73,78,143,143,134,18,201,116,104,79,44,29,202,74,133,250, | |||
| 215,135,82,35,209,68,2,109,215,254,107,109,71,99,187,163,211,227,78,29,209,209,232,100,26,10,202,122,166,39,38,14,100,249,27, | |||
| 163,233,116,119,116,124,124,87,116,228,66,18,125,100,244,245,147,217,215,223,79,149,125,91,67,235,247,143,196,38,211,241,36,130, | |||
| 249,88,124,60,22,26,25,79,166,226,137,61,161,201,228,84,154,106,251,182,190,89,253,68,124,52,142,33,236,139,143,196,72,108,34, | |||
| 107,211,246,238,245,84,184,105,122,36,54,128,154,190,196,228,116,122,27,171,8,100,88,91,167,211,25,158,47,195,147,79,197,153, | |||
| 167,161,233,73,238,181,97,111,116,95,148,68,63,25,253,125,100,246,247,201,15,244,128,15,100,22,24,182,217,143,15,171,191,127, | |||
| 103,63,213,244,71,19,163,83,201,248,104,227,174,204,108,27,179,243,238,84,230,104,163,5,111,37,213,35,231,208,70,85,111,37,196, | |||
| 38,108,163,69,103,18,201,88,185,141,26,207,40,58,22,157,138,142,96,120,241,84,58,62,210,70,139,207,212,160,39,150,26,153,138, | |||
| 79,166,147,83,179,15,100,60,54,35,223,31,27,82,190,52,251,220,33,202,245,51,163,125,19,125,44,180,33,62,142,65,214,116,77,199, | |||
| 199,71,89,223,108,102,58,73,244,45,69,6,99,41,184,236,236,179,213,34,67,177,116,26,14,150,154,233,242,45,166,144,17,110,163, | |||
| 121,89,161,145,100,34,29,75,164,27,187,153,238,71,103,149,217,170,137,216,104,60,218,200,174,219,200,14,151,89,250,37,111,45,208, | |||
| 151,216,157,172,97,87,229,130,115,56,111,42,221,70,181,111,45,52,148,142,166,167,49,234,234,55,19,203,110,32,167,43,157,34,163, | |||
| 163,67,141,82,57,179,154,43,207,212,96,107,66,53,217,58,25,75,196,70,251,225,129,49,233,43,161,51,52,124,139,185,207,236,110, | |||
| 231,250,159,34,52,24,27,137,197,247,177,158,162,172,72,50,213,216,53,157,24,29,199,50,20,59,153,189,81,102,66,180,196,201,221, | |||
| 22,157,26,137,141,111,159,142,143,182,81,32,91,49,157,142,143,55,246,39,247,156,198,219,22,141,79,57,250,202,242,218,104,251, | |||
| 233,204,246,51,184,201,25,227,3,14,130,166,254,145,228,68,227,212,68,106,188,113,47,162,90,227,41,161,173,230,212,200,222,70, | |||
| 205,103,104,113,90,68,109,163,165,255,98,19,231,154,44,249,23,219,40,233,254,51,72,207,88,37,235,131,111,122,226,180,81,207,191, | |||
| 173,109,134,195,46,26,137,166,46,60,179,161,78,211,114,230,73,103,38,188,45,154,30,227,48,241,150,210,188,89,71,163,227,251, | |||
| 226,23,54,34,180,38,177,129,113,40,54,174,79,232,3,177,123,60,154,194,134,14,206,34,211,199,145,88,215,87,205,82,63,16,155,216, | |||
| 165,5,98,16,169,152,69,100,40,190,39,129,136,49,133,93,82,54,75,117,100,108,42,121,49,154,206,233,231,179,179,49,158,108,116, | |||
| 28,220,109,84,168,216,227,209,196,158,70,61,142,34,7,171,15,113,82,218,43,224,96,110,221,181,55,54,146,62,153,55,148,158,194, | |||
| 76,179,221,72,158,236,58,186,139,247,111,185,131,61,21,219,221,120,78,44,122,225,96,108,119,108,42,150,64,146,80,241,86,181, | |||
| 188,249,101,181,220,141,157,83,83,209,3,28,150,50,61,157,204,109,163,238,217,216,237,255,206,106,175,225,67,111,86,37,167,77,119, | |||
| 77,214,8,51,162,169,147,121,189,209,20,118,244,100,198,170,78,222,233,130,56,179,78,19,4,239,100,19,244,225,36,141,202,179,190, | |||
| 192,193,149,54,241,159,194,104,163,150,83,56,237,103,60,128,215,156,172,87,118,95,232,96,68,226,19,236,16,115,78,101,169,173, | |||
| 88,120,218,94,163,206,211,88,179,39,173,142,211,36,148,58,128,131,103,34,148,138,77,201,44,50,112,250,174,39,159,115,209,168, | |||
| 214,121,228,55,116,119,246,247,119,117,118,111,190,32,114,238,182,245,23,12,116,70,186,123,47,232,223,58,20,33,177,131,140,29, | |||
| 200,26,119,32,207,181,118,244,237,236,35,215,142,77,200,35,55,129,141,236,113,7,210,74,107,7,231,149,246,14,201,5,71,126,176, | |||
| 116,191,170,68,217,230,207,77,138,32,23,221,177,147,4,210,79,40,51,144,119,26,195,93,84,61,124,230,84,168,126,248,223,74,45,106, | |||
| 254,5,113,236,221,225,89,246,233,73,204,204,70,205,141,142,140,196,82,169,13,227,209,61,41,242,34,221,156,142,142,203,156,219, | |||
| 157,185,42,152,209,209,81,126,26,157,130,28,249,116,239,125,137,209,216,126,180,86,79,178,133,55,58,57,169,51,42,114,69,83,202, | |||
| 19,119,157,146,106,83,89,150,211,191,94,238,61,181,182,219,183,247,245,80,96,215,105,233,169,67,67,198,145,138,103,56,217,105, | |||
| 167,28,114,23,232,59,71,206,174,116,167,30,181,103,87,90,201,65,76,151,82,124,160,195,4,228,218,149,230,195,136,236,93,156,77, | |||
| 146,111,68,159,74,145,3,147,49,114,97,20,72,39,40,127,228,164,100,156,236,145,241,88,116,138,73,50,21,35,55,18,202,4,108,76, | |||
| 185,186,32,21,122,56,205,140,198,19,41,201,150,165,205,177,3,82,88,218,200,167,11,145,228,118,232,176,177,11,18,105,18,163,228, | |||
| 29,205,230,241,228,210,115,241,40,10,27,101,74,163,148,151,41,41,5,185,163,89,7,72,101,234,50,38,243,170,71,153,236,228,140,198, | |||
| 167,48,68,132,125,176,227,169,204,208,93,177,139,176,244,41,202,145,155,178,59,57,10,3,198,50,7,4,53,236,142,226,106,55,26, | |||
| 74,39,67,35,83,177,104,58,22,218,53,61,174,239,148,74,119,104,247,84,114,34,148,113,19,207,238,120,34,58,30,127,71,140,170,80, | |||
| 26,157,89,168,13,201,41,199,237,75,9,87,178,72,102,67,207,38,96,239,142,79,193,153,124,187,97,162,209,204,130,123,185,67,229, | |||
| 198,100,237,97,131,231,240,167,50,134,137,72,66,94,124,100,84,228,114,121,92,186,118,138,202,248,65,121,238,105,215,242,249, | |||
| 51,117,167,199,176,57,92,57,57,57,30,31,145,167,106,198,219,139,192,62,109,208,165,78,166,51,167,151,90,78,191,136,145,7,108,121, | |||
| 246,82,33,74,61,234,238,158,217,54,57,146,37,125,33,63,91,84,107,237,205,62,167,200,141,178,116,190,69,40,244,78,79,112,56,199, | |||
| 70,198,225,171,44,53,171,117,33,10,199,146,100,84,106,96,189,84,141,2,31,144,167,25,99,75,116,130,153,125,61,41,170,59,93,70, | |||
| 102,161,167,9,134,79,23,84,185,231,105,146,243,32,201,213,167,14,19,147,155,171,171,122,178,206,140,233,232,33,179,6,185,200, | |||
| 188,194,242,33,47,243,48,205,185,19,249,245,35,31,19,220,172,71,218,91,249,131,20,157,74,78,198,166,210,113,244,83,128,199,193, | |||
| 216,68,50,29,203,4,13,48,134,228,81,164,163,149,236,82,6,136,188,49,121,11,209,247,22,114,143,69,83,91,216,37,60,40,140,201, | |||
| 93,100,141,37,225,187,57,252,169,124,83,196,201,140,143,238,39,43,206,102,182,227,114,17,115,226,217,247,33,185,241,84,118,242, | |||
| 252,208,173,118,104,12,19,141,167,214,79,76,166,15,112,65,218,153,171,103,94,164,120,226,58,37,32,15,167,55,189,220,175,111,175, | |||
| 243,69,138,121,33,2,144,11,31,156,97,120,199,147,136,117,42,144,187,39,180,135,91,124,158,144,119,34,107,102,42,156,56,109, | |||
| 27,228,79,156,180,10,148,59,225,8,196,198,196,4,153,19,169,61,248,72,79,147,149,224,181,176,249,19,81,33,17,187,152,247,0,140, | |||
| 146,96,35,153,201,93,123,201,149,220,189,59,133,225,4,146,137,174,104,122,100,108,38,7,73,81,9,246,216,73,129,23,79,137,61,176, | |||
| 68,241,169,21,236,230,52,231,84,238,57,83,48,137,212,162,108,136,61,43,251,87,106,200,159,76,204,188,51,145,26,10,157,28,213, | |||
| 58,47,169,239,194,112,68,244,156,159,60,233,106,204,125,58,159,123,98,227,209,3,96,23,100,216,236,72,251,156,114,42,8,100,38, | |||
| 226,78,38,54,140,79,167,198,200,151,76,12,164,167,51,108,140,140,199,163,188,112,48,149,138,83,41,115,198,227,188,149,229,184, | |||
| 186,147,19,147,136,192,144,69,75,153,80,200,8,157,121,82,22,132,113,145,13,37,164,189,180,235,166,122,56,230,227,138,13,217, | |||
| 34,184,124,226,148,24,69,94,102,234,114,30,151,103,28,172,132,31,79,186,106,158,19,79,143,97,43,149,102,42,102,46,148,186,38, | |||
| 144,169,113,240,242,153,231,120,217,151,195,207,106,39,122,146,153,188,46,39,83,66,128,194,224,248,16,75,206,52,177,147,23,115, | |||
| 200,44,154,132,251,157,58,129,178,89,152,67,233,216,100,228,226,36,149,156,84,55,19,76,200,154,228,244,209,146,239,52,115,38, | |||
| 101,186,197,251,194,51,169,51,47,85,146,129,37,63,83,210,17,75,214,200,236,51,47,83,82,27,93,86,200,40,145,159,41,69,146,27,112, | |||
| 214,145,61,41,103,107,242,22,158,59,21,219,195,239,87,166,78,126,73,67,174,41,233,57,228,85,84,133,6,85,86,249,214,188,41,28, | |||
| 217,177,84,122,198,183,183,77,197,147,240,141,3,220,86,46,191,123,74,111,36,48,210,251,162,227,100,77,177,47,153,83,211,9,42, | |||
| 76,101,179,80,253,30,141,138,82,142,236,57,195,116,103,94,58,123,82,35,99,177,81,28,251,228,74,197,144,54,140,146,149,98,223, | |||
| 42,227,79,245,182,119,44,58,26,234,219,26,154,201,27,60,92,199,102,166,2,236,241,110,103,106,149,11,6,123,234,0,7,201,124,126, | |||
| 208,153,224,116,124,20,149,99,124,41,192,94,193,68,173,20,39,18,118,74,62,228,72,194,13,41,79,21,211,201,73,249,232,74,169,227, | |||
| 213,74,129,131,158,51,252,28,120,79,102,149,211,99,113,24,131,63,107,154,80,129,11,11,26,77,76,146,59,157,148,183,54,242,164, | |||
| 147,58,167,152,51,157,152,205,187,230,157,194,118,248,80,233,116,226,77,214,210,134,237,167,113,58,72,178,117,55,69,196,205, | |||
| 194,157,111,188,76,109,251,141,171,47,109,171,167,152,120,15,24,116,129,36,135,76,58,34,44,218,73,224,124,85,8,178,172,7,140,213, | |||
| 163,238,252,19,38,221,111,228,237,180,137,62,33,196,231,89,254,22,97,124,68,220,111,184,243,47,236,55,233,54,97,213,95,111,83, | |||
| 199,254,126,23,53,28,121,7,196,62,32,164,190,195,82,95,195,254,16,237,21,223,54,220,75,32,251,1,97,54,24,21,23,27,123,42,250, | |||
| 77,241,65,145,211,240,158,134,157,166,241,160,145,251,225,157,166,249,85,35,127,243,206,142,71,250,182,218,134,109,210,141,74, | |||
| 201,17,250,190,176,94,23,135,196,103,140,103,240,216,94,143,63,237,244,186,32,119,197,150,183,111,62,80,95,111,76,87,84,154, | |||
| 244,5,209,64,199,193,204,111,111,167,163,6,207,224,105,126,162,107,101,249,253,134,245,119,113,153,113,139,248,33,198,92,127, | |||
| 11,221,96,152,234,25,117,207,177,220,163,59,59,232,165,76,225,78,195,84,29,170,238,232,167,198,44,157,221,107,168,206,94,146, | |||
| 29,124,69,126,126,111,70,237,150,11,140,75,50,162,95,151,149,223,146,159,55,153,6,6,143,65,180,215,211,189,166,241,21,113,35, | |||
| 143,225,30,211,228,210,227,232,145,62,239,40,127,154,203,143,27,255,132,76,199,230,15,211,23,249,241,211,170,234,1,71,249,33, | |||
| 46,255,67,149,31,228,242,151,13,89,254,50,119,32,75,119,103,75,223,54,45,250,140,184,77,60,8,157,59,121,118,199,76,140,171,163, | |||
| 29,139,243,37,99,109,255,206,225,53,91,206,95,83,111,147,177,191,205,69,244,146,172,236,143,155,226,57,81,116,224,17,185,160, | |||
| 245,231,219,100,139,249,149,171,232,160,197,83,122,183,252,124,15,127,118,28,218,31,44,167,235,45,118,179,10,227,176,213,102, | |||
| 188,126,201,146,250,71,251,141,252,139,141,125,21,251,247,239,63,16,71,55,162,91,233,91,189,198,22,244,126,151,92,102,17,240, | |||
| 91,198,171,162,178,243,144,179,171,111,115,79,182,65,71,181,208,92,191,73,183,139,38,200,28,53,106,111,227,74,186,220,195,253, | |||
| 30,54,141,255,21,221,65,147,158,21,66,184,109,50,5,10,143,88,38,107,20,134,45,92,36,114,109,114,137,74,219,172,151,26,127,105, | |||
| 137,187,97,142,142,120,229,176,101,220,105,44,31,22,197,126,83,220,110,212,237,55,14,124,131,37,214,187,12,140,245,255,196,122, | |||
| 186,218,35,238,224,5,16,129,2,139,88,225,175,67,54,205,175,60,139,254,100,153,183,139,95,139,63,112,101,187,153,243,89,67,244, | |||
| 155,38,84,52,30,50,106,150,24,219,43,108,211,206,89,238,50,93,57,123,45,247,221,104,215,176,217,116,221,38,10,27,224,22,47,137, | |||
| 5,13,123,77,227,19,198,188,122,12,143,118,217,6,246,206,173,205,152,145,105,187,108,183,113,17,140,143,150,46,151,123,175,233, | |||
| 249,173,152,35,165,132,233,34,195,87,1,33,136,216,158,74,186,7,54,175,24,238,24,22,115,11,48,118,209,244,77,91,172,110,175,228, | |||
| 39,227,101,81,7,147,218,70,3,27,22,91,180,178,179,127,255,59,174,180,105,184,157,94,112,203,169,99,222,247,24,203,119,30,49, | |||
| 177,3,138,238,229,185,7,226,183,236,91,213,191,200,22,219,176,135,239,182,217,228,21,29,123,227,43,140,253,21,135,154,91,227, | |||
| 13,149,116,92,46,245,143,228,231,119,221,226,86,168,57,96,186,49,241,142,251,228,178,5,77,227,21,33,110,189,213,180,160,13,179, | |||
| 189,219,16,152,180,121,155,33,246,110,54,237,59,140,165,113,225,181,237,6,151,93,203,38,198,92,45,219,182,93,198,72,133,237,94, | |||
| 238,18,46,195,101,241,148,141,75,218,80,225,50,74,54,177,20,252,236,117,183,113,183,113,59,59,64,113,129,73,159,54,234,46,196, | |||
| 0,239,113,161,71,119,254,37,229,244,172,75,60,193,107,185,217,244,96,12,232,246,118,33,130,166,251,43,194,174,52,115,158,23, | |||
| 107,159,57,16,124,196,180,57,20,109,54,173,235,196,210,91,133,199,118,215,163,159,242,139,121,0,247,154,185,240,166,251,68,161, | |||
| 223,206,13,26,163,21,24,132,125,139,229,125,141,87,108,111,67,251,218,117,182,119,133,26,40,91,221,206,89,197,99,116,121,92,57, | |||
| 174,92,35,221,102,231,178,60,125,204,173,198,0,71,68,239,143,30,62,193,219,208,62,98,4,111,182,205,160,177,187,2,61,99,108, | |||
| 143,152,66,13,131,48,140,67,150,45,251,232,119,153,13,237,93,114,233,237,205,77,210,48,22,61,160,102,215,113,216,34,237,58,240, | |||
| 229,194,1,83,200,89,89,247,25,107,59,130,188,29,120,161,15,139,194,130,96,53,214,186,125,205,48,154,195,170,193,114,24,232, | |||
| 105,151,218,241,159,178,13,118,83,148,110,179,5,91,145,224,224,71,132,123,137,26,134,49,103,204,72,84,116,196,141,188,37,198, | |||
| 190,182,91,132,223,47,59,105,248,72,195,60,250,133,20,204,143,211,29,46,185,216,59,233,25,233,22,249,70,120,204,24,174,232,56, | |||
| 124,136,227,138,141,142,207,179,13,172,48,236,200,147,131,5,218,113,34,108,182,172,215,102,198,126,73,167,109,194,139,97,72, | |||
| 172,47,124,27,86,171,52,5,199,199,191,27,230,157,226,179,226,189,58,188,211,251,77,113,208,112,87,180,204,251,98,189,73,55,25, | |||
| 117,116,136,163,16,182,159,73,191,17,77,159,183,169,29,145,233,81,236,251,138,122,106,103,87,252,130,71,124,10,190,138,16,118, | |||
| 88,152,134,111,137,113,113,197,133,29,155,247,195,115,110,145,53,249,135,106,141,119,84,40,142,17,22,63,54,74,235,140,69,198, | |||
| 127,11,43,231,31,162,36,215,88,12,78,185,85,114,126,137,93,50,84,226,81,143,118,137,40,137,130,177,164,36,71,138,214,228,184, | |||
| 32,91,58,211,108,174,177,132,229,68,233,210,25,94,137,82,94,171,56,6,56,249,51,197,248,140,220,30,227,44,110,107,148,214,148, | |||
| 46,200,116,119,158,236,189,102,166,127,102,156,93,178,33,195,112,151,156,3,198,26,160,9,82,222,12,147,165,122,75,182,227,179, | |||
| 39,195,244,96,232,67,37,102,86,54,171,209,144,42,122,51,12,23,24,51,50,114,112,94,12,110,175,26,156,171,180,182,116,97,105,85, | |||
| 105,168,180,178,180,90,148,88,194,20,57,102,153,129,63,194,104,57,120,208,58,182,112,153,56,88,39,196,109,192,147,192,225,48, | |||
| 220,30,56,14,60,185,72,136,155,206,18,130,255,145,50,185,182,93,230,33,49,11,200,54,114,92,250,135,56,130,58,46,59,104,189,123, | |||
| 137,117,185,65,107,196,189,75,132,117,125,189,16,15,212,27,226,231,160,47,3,239,110,16,226,30,224,81,224,96,35,2,188,59,87,182, | |||
| 235,69,187,7,27,251,196,175,26,133,245,104,147,16,207,2,135,155,133,184,17,248,21,240,151,102,50,132,55,223,16,87,135,118,64, | |||
| 244,240,210,115,196,109,75,133,120,16,56,6,60,11,92,223,34,196,93,192,195,192,147,192,243,192,27,45,100,9,219,143,201,10,110, | |||
| 26,69,211,171,151,237,18,15,44,195,8,150,11,241,216,10,104,7,14,175,36,183,59,80,172,196,244,223,61,144,125,114,165,97,92,181, | |||
| 74,24,215,174,54,141,55,86,11,227,141,54,211,184,171,195,107,220,180,102,204,122,99,173,41,30,239,130,165,186,77,241,100,15, | |||
| 102,215,99,136,171,214,99,164,27,48,132,141,120,6,158,221,4,221,3,232,99,11,248,192,213,91,13,113,207,86,240,183,193,18,103,195, | |||
| 186,103,195,2,198,124,193,127,14,10,116,120,253,208,101,152,212,16,38,19,225,159,231,5,189,194,251,46,113,248,160,245,66,132, | |||
| 107,15,111,23,57,55,1,87,237,200,252,255,74,206,223,244,100,254,239,64,254,173,74,230,255,15,228,223,169,100,254,15,65,254,157, | |||
| 74,136,212,255,35,200,191,213,201,252,95,130,46,154,249,255,4,77,191,250,29,141,252,61,85,72,253,63,82,219,192,112,133,148,12, | |||
| 255,123,122,225,87,191,125,231,127,3,111,132,84,191,252,255,15,154,90,158,255,141,182,21,82,191,75,226,127,199,109,135,212,248, | |||
| 248,223,224,147,214,195,255,38,159,127,204,195,124,254,127,15,255,63,171,27,97,244,48,81,0,0,0,0}; | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \ | |||
| STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;") | |||
| DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23, javaMidiByteCode, sizeof (javaMidiByteCode)) | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (getJuceAndroidMidiInputDeviceNameAndIDs, "getJuceAndroidMidiInputDeviceNameAndIDs", "()[Ljava/lang/String;") \ | |||
| METHOD (getJuceAndroidMidiOutputDeviceNameAndIDs, "getJuceAndroidMidiOutputDeviceNameAndIDs", "()[Ljava/lang/String;") \ | |||
| METHOD (openMidiInputPortWithID, "openMidiInputPortWithID", "(IJ)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") \ | |||
| METHOD (openMidiOutputPortWithID, "openMidiOutputPortWithID", "(I)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") | |||
| DECLARE_JNI_CLASS_WITH_MIN_SDK (MidiDeviceManager, "com/rmsl/juce/JuceMidiSupport$MidiDeviceManager", 23) | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (start, "start", "()V") \ | |||
| METHOD (stop, "stop", "()V") \ | |||
| METHOD (close, "close", "()V") \ | |||
| METHOD (sendMidi, "sendMidi", "([BII)V") \ | |||
| METHOD (getName, "getName", "()Ljava/lang/String;") | |||
| DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiPort", 23) | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| class MidiInput::Pimpl | |||
| { | |||
| public: | |||
| Pimpl (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager) | |||
| : juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048), | |||
| javaMidiDevice (LocalRef<jobject>(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID, | |||
| (jint) deviceID, (jlong) this))) | |||
| { | |||
| } | |||
| ~Pimpl() | |||
| { | |||
| if (jobject d = javaMidiDevice.get()) | |||
| { | |||
| getEnv()->CallVoidMethod (d, JuceMidiPort.close); | |||
| javaMidiDevice.clear(); | |||
| } | |||
| } | |||
| bool isOpen() const noexcept | |||
| { | |||
| return javaMidiDevice != nullptr; | |||
| } | |||
| void start() | |||
| { | |||
| if (jobject d = javaMidiDevice.get()) | |||
| getEnv()->CallVoidMethod (d, JuceMidiPort.start); | |||
| } | |||
| void stop() | |||
| { | |||
| if (jobject d = javaMidiDevice.get()) | |||
| getEnv()->CallVoidMethod (d, JuceMidiPort.stop); | |||
| callback = nullptr; | |||
| } | |||
| String getName() const noexcept | |||
| { | |||
| if (jobject d = javaMidiDevice.get()) | |||
| return juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); | |||
| return {}; | |||
| } | |||
| void handleMidi (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) | |||
| { | |||
| auto* env = getEnv(); | |||
| jassert (byteArray != nullptr); | |||
| auto* data = env->GetByteArrayElements (byteArray, nullptr); | |||
| HeapBlock<uint8> buffer (static_cast<size_t> (len)); | |||
| std::memcpy (buffer.get(), data + offset, static_cast<size_t> (len)); | |||
| midiConcatenator.pushMidiData (buffer.get(), | |||
| len, static_cast<double> (timestamp) * 1.0e-9, | |||
| juceMidiInput, *callback); | |||
| env->ReleaseByteArrayElements (byteArray, data, 0); | |||
| } | |||
| static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray, | |||
| jint offset, jint len, jlong timestamp) | |||
| { | |||
| auto* myself = reinterpret_cast<Pimpl*> (host); | |||
| myself->handleMidi (byteArray, offset, len, timestamp); | |||
| } | |||
| private: | |||
| MidiInput* juceMidiInput; | |||
| MidiInputCallback* callback; | |||
| MidiDataConcatenator midiConcatenator; | |||
| GlobalRef javaMidiDevice; | |||
| }; | |||
| //============================================================================== | |||
| class MidiOutput::Pimpl | |||
| { | |||
| public: | |||
| Pimpl (const LocalRef<jobject>& midiDevice) | |||
| : javaMidiDevice (midiDevice) | |||
| { | |||
| } | |||
| ~Pimpl() | |||
| { | |||
| if (jobject d = javaMidiDevice.get()) | |||
| { | |||
| getEnv()->CallVoidMethod (d, JuceMidiPort.close); | |||
| javaMidiDevice.clear(); | |||
| } | |||
| } | |||
| void send (jbyteArray byteArray, jint offset, jint len) | |||
| { | |||
| if (jobject d = javaMidiDevice.get()) | |||
| getEnv()->CallVoidMethod (d, | |||
| JuceMidiPort.sendMidi, | |||
| byteArray, offset, len); | |||
| } | |||
| String getName() const noexcept | |||
| { | |||
| if (jobject d = javaMidiDevice.get()) | |||
| return juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); | |||
| return {}; | |||
| } | |||
| private: | |||
| GlobalRef javaMidiDevice; | |||
| }; | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| CALLBACK (MidiInput::Pimpl::handleReceive, "handleReceive", "(J[BIIJ)V" ) | |||
| DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiInputPort", 23) | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| class AndroidMidiDeviceManager | |||
| { | |||
| public: | |||
| AndroidMidiDeviceManager() | |||
| : deviceManager (LocalRef<jobject>(getEnv()->CallStaticObjectMethod (JuceMidiSupport, | |||
| JuceMidiSupport.getAndroidMidiDeviceManager, | |||
| getAppContext().get()))) | |||
| { | |||
| } | |||
| Array<MidiDeviceInfo> getDevices (bool input) | |||
| { | |||
| if (jobject dm = deviceManager.get()) | |||
| { | |||
| jobjectArray jDeviceNameAndIDs | |||
| = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDeviceNameAndIDs | |||
| : MidiDeviceManager.getJuceAndroidMidiOutputDeviceNameAndIDs); | |||
| // Create a local reference as converting this to a JUCE string will call into JNI | |||
| LocalRef<jobjectArray> localDeviceNameAndIDs (jDeviceNameAndIDs); | |||
| auto deviceNameAndIDs = javaStringArrayToJuce (localDeviceNameAndIDs); | |||
| deviceNameAndIDs.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); | |||
| Array<MidiDeviceInfo> devices; | |||
| for (int i = 0; i < deviceNameAndIDs.size(); i += 2) | |||
| devices.add ({ deviceNameAndIDs[i], deviceNameAndIDs[i + 1] }); | |||
| return devices; | |||
| } | |||
| return {}; | |||
| } | |||
| MidiInput::Pimpl* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) | |||
| { | |||
| if (auto dm = deviceManager.get()) | |||
| { | |||
| auto androidMidiInput = std::make_unique<MidiInput::Pimpl> (juceMidiInput, deviceID, callback, dm); | |||
| if (androidMidiInput->isOpen()) | |||
| return androidMidiInput.release(); | |||
| } | |||
| return nullptr; | |||
| } | |||
| MidiOutput::Pimpl* openMidiOutputPortWithID (int deviceID) | |||
| { | |||
| if (auto dm = deviceManager.get()) | |||
| if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID)) | |||
| return new MidiOutput::Pimpl (LocalRef<jobject>(javaMidiPort)); | |||
| return nullptr; | |||
| } | |||
| private: | |||
| GlobalRef deviceManager; | |||
| }; | |||
| //============================================================================== | |||
| Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | |||
| { | |||
| if (getAndroidSDKVersion() < 23) | |||
| return {}; | |||
| AndroidMidiDeviceManager manager; | |||
| return manager.getDevices (true); | |||
| } | |||
| MidiDeviceInfo MidiInput::getDefaultDevice() | |||
| { | |||
| if (getAndroidSDKVersion() < 23) | |||
| return {}; | |||
| return getAvailableDevices().getFirst(); | |||
| } | |||
| std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) | |||
| { | |||
| if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) | |||
| return {}; | |||
| AndroidMidiDeviceManager manager; | |||
| std::unique_ptr<MidiInput> midiInput (new MidiInput ({}, deviceIdentifier)); | |||
| if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback)) | |||
| { | |||
| midiInput->internal.reset (port); | |||
| midiInput->setName (port->getName()); | |||
| return midiInput; | |||
| } | |||
| return {}; | |||
| } | |||
| StringArray MidiInput::getDevices() | |||
| { | |||
| if (getAndroidSDKVersion() < 23) | |||
| return {}; | |||
| StringArray deviceNames; | |||
| for (auto& d : getAvailableDevices()) | |||
| deviceNames.add (d.name); | |||
| return deviceNames; | |||
| } | |||
| int MidiInput::getDefaultDeviceIndex() | |||
| { | |||
| return (getAndroidSDKVersion() < 23 ? -1 : 0); | |||
| } | |||
| std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback) | |||
| { | |||
| return openDevice (getAvailableDevices()[index].identifier, callback); | |||
| } | |||
| MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) | |||
| : deviceInfo (deviceName, deviceIdentifier) | |||
| { | |||
| } | |||
| MidiInput::~MidiInput() = default; | |||
| void MidiInput::start() | |||
| { | |||
| if (auto* mi = internal.get()) | |||
| mi->start(); | |||
| } | |||
| void MidiInput::stop() | |||
| { | |||
| if (auto* mi = internal.get()) | |||
| mi->stop(); | |||
| } | |||
| //============================================================================== | |||
| Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | |||
| { | |||
| if (getAndroidSDKVersion() < 23) | |||
| return {}; | |||
| AndroidMidiDeviceManager manager; | |||
| return manager.getDevices (false); | |||
| } | |||
| MidiDeviceInfo MidiOutput::getDefaultDevice() | |||
| { | |||
| if (getAndroidSDKVersion() < 23) | |||
| return {}; | |||
| return getAvailableDevices().getFirst(); | |||
| } | |||
| std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier) | |||
| { | |||
| if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) | |||
| return {}; | |||
| AndroidMidiDeviceManager manager; | |||
| if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue())) | |||
| { | |||
| std::unique_ptr<MidiOutput> midiOutput (new MidiOutput ({}, deviceIdentifier)); | |||
| midiOutput->internal.reset (port); | |||
| midiOutput->setName (port->getName()); | |||
| return midiOutput; | |||
| } | |||
| return {}; | |||
| } | |||
| StringArray MidiOutput::getDevices() | |||
| { | |||
| if (getAndroidSDKVersion() < 23) | |||
| return {}; | |||
| StringArray deviceNames; | |||
| for (auto& d : getAvailableDevices()) | |||
| deviceNames.add (d.name); | |||
| return deviceNames; | |||
| } | |||
| int MidiOutput::getDefaultDeviceIndex() | |||
| { | |||
| return (getAndroidSDKVersion() < 23 ? -1 : 0); | |||
| } | |||
| std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||
| { | |||
| return openDevice (getAvailableDevices()[index].identifier); | |||
| } | |||
| MidiOutput::~MidiOutput() | |||
| { | |||
| stopBackgroundThread(); | |||
| } | |||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| { | |||
| if (auto* androidMidi = internal.get()) | |||
| { | |||
| auto* env = getEnv(); | |||
| auto messageSize = message.getRawDataSize(); | |||
| LocalRef<jbyteArray> messageContent (env->NewByteArray (messageSize)); | |||
| auto content = messageContent.get(); | |||
| auto* rawBytes = env->GetByteArrayElements (content, nullptr); | |||
| std::memcpy (rawBytes, message.getRawData(), static_cast<size_t> (messageSize)); | |||
| env->ReleaseByteArrayElements (content, rawBytes, 0); | |||
| androidMidi->send (content, (jint) 0, (jint) messageSize); | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -1,92 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class iOSAudioIODeviceType; | |||
| class iOSAudioIODevice : public AudioIODevice | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| String open (const BigInteger&, const BigInteger&, double, int) override; | |||
| void close() override; | |||
| void start (AudioIODeviceCallback*) override; | |||
| void stop() override; | |||
| Array<double> getAvailableSampleRates() override; | |||
| Array<int> getAvailableBufferSizes() override; | |||
| bool setAudioPreprocessingEnabled (bool) override; | |||
| //============================================================================== | |||
| bool isPlaying() override; | |||
| bool isOpen() override; | |||
| String getLastError() override; | |||
| //============================================================================== | |||
| StringArray getOutputChannelNames() override; | |||
| StringArray getInputChannelNames() override; | |||
| int getDefaultBufferSize() override; | |||
| int getCurrentBufferSizeSamples() override; | |||
| double getCurrentSampleRate() override; | |||
| int getCurrentBitDepth() override; | |||
| BigInteger getActiveOutputChannels() const override; | |||
| BigInteger getActiveInputChannels() const override; | |||
| int getOutputLatencyInSamples() override; | |||
| int getInputLatencyInSamples() override; | |||
| int getXRunCount() const noexcept override; | |||
| //============================================================================== | |||
| void setMidiMessageCollector (MidiMessageCollector*); | |||
| AudioPlayHead* getAudioPlayHead() const; | |||
| //============================================================================== | |||
| bool isInterAppAudioConnected() const; | |||
| #if JUCE_MODULE_AVAILABLE_juce_graphics | |||
| Image getIcon (int size); | |||
| #endif | |||
| void switchApplication(); | |||
| private: | |||
| //============================================================================== | |||
| iOSAudioIODevice (iOSAudioIODeviceType*, const String&, const String&); | |||
| //============================================================================== | |||
| friend class iOSAudioIODeviceType; | |||
| friend struct AudioSessionHolder; | |||
| struct Pimpl; | |||
| std::unique_ptr<Pimpl> pimpl; | |||
| JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice) | |||
| }; | |||
| } // namespace juce | |||
| @@ -52,7 +52,7 @@ FUNCTION_TEMPLATE = """/* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2020 - Raw Material Software Limited | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| @@ -79,6 +79,8 @@ FUNCTION_TEMPLATE = """/* | |||
| #pragma once | |||
| #ifndef DOXYGEN | |||
| #include <vector> | |||
| namespace juce | |||
| @@ -109,7 +111,8 @@ std::vector<juce::lv2::Bundle> juce::lv2::Bundle::getAllBundles() | |||
| {} | |||
| }}; | |||
| }} | |||
| """ | |||
| #endif""" | |||
| def chunks(lst, n): | |||
| @@ -149,5 +152,6 @@ args = parser.parse_args() | |||
| print(FUNCTION_TEMPLATE.format(", ".join(generate_bundle_source(root, files) | |||
| for root, files in filter_ttl_files(args.lv2_dir) | |||
| if len(files) != 0)), | |||
| if len(files) != 0)) | |||
| .replace("\t", " "), | |||
| end = "\r\n") | |||
| @@ -102,17 +102,26 @@ struct ConversionFunctions | |||
| }; | |||
| //============================================================================== | |||
| /** This class is used by the various ARA model object helper classes, such as MusicalContext, | |||
| AudioSource etc. It helps with deregistering the model objects from the DocumentController | |||
| when the lifetime of the helper class object ends. | |||
| You shouldn't use this class directly but instead inherit from the helper classes. | |||
| */ | |||
| template <typename Base, typename PtrIn> | |||
| class ManagedARAHandle | |||
| { | |||
| public: | |||
| using Ptr = PtrIn; | |||
| /** Constructor. */ | |||
| ManagedARAHandle (ARA::Host::DocumentController& dc, Ptr ptr) noexcept | |||
| : handle (ptr, Deleter { dc }) {} | |||
| /** Returns the host side DocumentController reference. */ | |||
| auto& getDocumentController() const { return handle.get_deleter().documentController; } | |||
| /** Returns the plugin side reference to the model object. */ | |||
| Ptr getPluginRef() const { return handle.get(); } | |||
| private: | |||
| @@ -268,12 +277,34 @@ private: | |||
| AudioSource& source; | |||
| }; | |||
| /** This class is used internally by PlaybackRegionRegistry to be notified when a PlaybackRegion | |||
| object is deleted. | |||
| */ | |||
| struct DeletionListener | |||
| { | |||
| /** Destructor. */ | |||
| virtual ~DeletionListener() = default; | |||
| /** Removes another DeletionListener object from this DeletionListener. */ | |||
| virtual void removeListener (DeletionListener& other) noexcept = 0; | |||
| }; | |||
| /** Helper class for the host side implementation of the %ARA %PlaybackRegion model object. | |||
| Its intended use is to add a member variable of this type to your host side %PlaybackRegion | |||
| implementation. Then it provides a RAII approach to managing the lifetime of the corresponding | |||
| objects created inside the DocumentController. When the host side object is instantiated an ARA | |||
| model object is also created in the DocumentController. When the host side object is deleted it | |||
| will be removed from the DocumentController as well. | |||
| The class will automatically put the DocumentController into editable state for operations that | |||
| mandate this e.g. creation, deletion or updating. | |||
| You can encapsulate multiple such operations into a scope with an ARAEditGuard in order to invoke | |||
| the editable state of the DocumentController only once. | |||
| @tags{ARA} | |||
| */ | |||
| struct PlaybackRegion | |||
| { | |||
| public: | |||
| @@ -23,6 +23,8 @@ | |||
| ============================================================================== | |||
| */ | |||
| #ifndef DOXYGEN | |||
| // This macro can be set if you need to override this internal name for some reason.. | |||
| #ifndef JUCE_STATE_DICTIONARY_KEY | |||
| #define JUCE_STATE_DICTIONARY_KEY "jucePluginState" | |||
| @@ -31,8 +33,6 @@ | |||
| namespace juce | |||
| { | |||
| #ifndef DOXYGEN | |||
| struct AudioUnitHelpers | |||
| { | |||
| class ChannelRemapper | |||
| @@ -560,6 +560,6 @@ struct AudioUnitHelpers | |||
| } | |||
| }; | |||
| #endif | |||
| } // namespace juce | |||
| #endif | |||
| @@ -1385,7 +1385,10 @@ public: | |||
| void processAudio (AudioBuffer<float>& buffer, MidiBuffer& midiMessages, bool processBlockBypassedCalled) | |||
| { | |||
| if (const auto* hostTimeNs = getHostTimeNs()) | |||
| auto* playhead = getPlayHead(); | |||
| const auto position = playhead != nullptr ? playhead->getPosition() : nullopt; | |||
| if (const auto hostTimeNs = position.hasValue() ? position->getHostTimeNs() : nullopt) | |||
| { | |||
| timeStamp.mHostTime = *hostTimeNs; | |||
| timeStamp.mFlags |= kAudioTimeStampHostTimeValid; | |||
| @@ -2298,12 +2301,10 @@ private: | |||
| { | |||
| if (auto* ph = getPlayHead()) | |||
| { | |||
| AudioPlayHead::CurrentPositionInfo result; | |||
| if (ph->getCurrentPosition (result)) | |||
| if (const auto pos = ph->getPosition()) | |||
| { | |||
| setIfNotNull (outCurrentBeat, result.ppqPosition); | |||
| setIfNotNull (outCurrentTempo, result.bpm); | |||
| setIfNotNull (outCurrentBeat, pos->getPpqPosition().orFallback (0.0)); | |||
| setIfNotNull (outCurrentTempo, pos->getBpm().orFallback (0.0)); | |||
| return noErr; | |||
| } | |||
| } | |||
| @@ -2318,14 +2319,13 @@ private: | |||
| { | |||
| if (auto* ph = getPlayHead()) | |||
| { | |||
| AudioPlayHead::CurrentPositionInfo result; | |||
| if (ph->getCurrentPosition (result)) | |||
| if (const auto pos = ph->getPosition()) | |||
| { | |||
| const auto signature = pos->getTimeSignature().orFallback (AudioPlayHead::TimeSignature{}); | |||
| setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); //xxx | |||
| setIfNotNull (outTimeSig_Numerator, (UInt32) result.timeSigNumerator); | |||
| setIfNotNull (outTimeSig_Denominator, (UInt32) result.timeSigDenominator); | |||
| setIfNotNull (outCurrentMeasureDownBeat, result.ppqPositionOfLastBarStart); //xxx wrong | |||
| setIfNotNull (outTimeSig_Numerator, (UInt32) signature.numerator); | |||
| setIfNotNull (outTimeSig_Denominator, (UInt32) signature.denominator); | |||
| setIfNotNull (outCurrentMeasureDownBeat, pos->getPpqPositionOfLastBarStart().orFallback (0.0)); //xxx wrong | |||
| return noErr; | |||
| } | |||
| } | |||
| @@ -25,8 +25,9 @@ | |||
| #pragma once | |||
| #ifndef DOXYGEN | |||
| #include "juce_lv2_config.h" | |||
| #include "juce_core/containers/juce_Optional.h" | |||
| #ifdef Bool | |||
| #undef Bool // previously defined in X11/Xlib.h | |||
| @@ -135,16 +136,6 @@ struct ObjectTraits { static constexpr auto construct = lv2_atom_forge_object; | |||
| using SequenceFrame = ScopedFrame<SequenceTraits>; | |||
| using ObjectFrame = ScopedFrame<ObjectTraits>; | |||
| template <typename Value, typename Callback> | |||
| bool withValue (const Optional<Value>& opt, Callback&& callback) | |||
| { | |||
| if (! opt.hasValue()) | |||
| return false; | |||
| callback (*opt); | |||
| return true; | |||
| } | |||
| struct NumericAtomParser | |||
| { | |||
| explicit NumericAtomParser (LV2_URID_Map mapFeatureIn) | |||
| @@ -626,3 +617,5 @@ static inline std::vector<ParsedGroup> findStableBusOrder (const String& mainGro | |||
| } | |||
| } | |||
| #endif | |||
| @@ -658,7 +658,7 @@ public: | |||
| const auto index = strings.size(); | |||
| indices.insert (it, index); | |||
| strings.emplace_back (uri); | |||
| strings.push_back (uriString); | |||
| return static_cast<LV2_URID> (index + 1); | |||
| } | |||
| @@ -724,6 +724,7 @@ struct UsefulUrids | |||
| X (LV2_TIME__beatsPerMinute) | |||
| X (LV2_TIME__frame) | |||
| X (LV2_TIME__speed) | |||
| X (LV2_TIME__bar) | |||
| X (LV2_UI__floatProtocol) | |||
| X (LV2_UNITS__beat) | |||
| X (LV2_UNITS__frame) | |||
| @@ -1696,11 +1697,13 @@ static SingleSizeAlignedStorage<Alignment> grow (SingleSizeAlignedStorage<Alignm | |||
| return newStorage; | |||
| } | |||
| enum class SupportsTime { no, yes }; | |||
| class AtomPort | |||
| { | |||
| public: | |||
| AtomPort (PortHeader h, size_t bytes, SymbolMap& map) | |||
| : header (h), contents (bytes), forge (map.getMapFeature()) {} | |||
| AtomPort (PortHeader h, size_t bytes, SymbolMap& map, SupportsTime supportsTime) | |||
| : header (h), contents (bytes), forge (map.getMapFeature()), time (supportsTime) {} | |||
| PortHeader header; | |||
| @@ -1766,6 +1769,8 @@ public: | |||
| lv2_shared::AtomForge& getForge() { return forge; } | |||
| const lv2_shared::AtomForge& getForge() const { return forge; } | |||
| bool getSupportsTime() const { return time == SupportsTime::yes; } | |||
| private: | |||
| template <typename This> | |||
| static auto data (This& t) -> decltype (t.data()) | |||
| @@ -1777,112 +1782,9 @@ private: | |||
| SingleSizeAlignedStorage<8> contents; | |||
| lv2_shared::AtomForge forge; | |||
| LV2_Atom_Forge_Frame frame; | |||
| SupportsTime time = SupportsTime::no; | |||
| }; | |||
| template <typename Value> | |||
| struct ParseResult | |||
| { | |||
| Value value; | |||
| bool successful; | |||
| }; | |||
| class Ports | |||
| { | |||
| public: | |||
| static constexpr auto sequenceSize = 8192; | |||
| template <typename Callback> | |||
| void forEachPort (Callback&& callback) const | |||
| { | |||
| for (const auto& port : controlPorts) | |||
| callback (port.header); | |||
| for (const auto& port : cvPorts) | |||
| callback (port.header); | |||
| for (const auto& port : audioPorts) | |||
| callback (port.header); | |||
| for (const auto& port : atomPorts) | |||
| callback (port.header); | |||
| } | |||
| auto getControlPorts() { return makeSimpleSpan (controlPorts); } | |||
| auto getControlPorts() const { return makeSimpleSpan (controlPorts); } | |||
| auto getCvPorts() { return makeSimpleSpan (cvPorts); } | |||
| auto getCvPorts() const { return makeSimpleSpan (cvPorts); } | |||
| auto getAudioPorts() { return makeSimpleSpan (audioPorts); } | |||
| auto getAudioPorts() const { return makeSimpleSpan (audioPorts); } | |||
| auto getAtomPorts() { return makeSimpleSpan (atomPorts); } | |||
| auto getAtomPorts() const { return makeSimpleSpan (atomPorts); } | |||
| private: | |||
| friend ParseResult<Ports> getPorts (const UsefulUris& uris, const Plugin& plugin, SymbolMap& symap); | |||
| std::vector<ControlPort> controlPorts; | |||
| std::vector<CVPort> cvPorts; | |||
| std::vector<AudioPort> audioPorts; | |||
| std::vector<AtomPort> atomPorts; | |||
| }; | |||
| ParseResult<Ports> getPorts (const UsefulUris& uris, const Plugin& plugin, SymbolMap& symap) | |||
| { | |||
| Ports value; | |||
| bool successful = true; | |||
| const auto numPorts = plugin.getNumPorts(); | |||
| for (uint32_t i = 0; i != numPorts; ++i) | |||
| { | |||
| const auto port = plugin.getPortByIndex (i); | |||
| const PortHeader header { String::fromUTF8 (port.getName().getTyped()), | |||
| String::fromUTF8 (port.getSymbol().getTyped()), | |||
| i, | |||
| port.getDirection (uris) }; | |||
| switch (port.getKind (uris)) | |||
| { | |||
| case Port::Kind::control: | |||
| { | |||
| value.controlPorts.push_back ({ header, ParameterInfo::getInfoForPort (uris, port) }); | |||
| break; | |||
| } | |||
| case Port::Kind::cv: | |||
| value.cvPorts.push_back ({ header }); | |||
| break; | |||
| case Port::Kind::audio: | |||
| { | |||
| value.audioPorts.push_back ({ header }); | |||
| break; | |||
| } | |||
| case Port::Kind::atom: | |||
| { | |||
| value.atomPorts.push_back ({ header, (size_t) Ports::sequenceSize, symap }); | |||
| break; | |||
| } | |||
| case Port::Kind::unknown: | |||
| successful = false; | |||
| break; | |||
| } | |||
| } | |||
| for (auto& atomPort : value.atomPorts) | |||
| { | |||
| const auto port = plugin.getPortByIndex (atomPort.header.index); | |||
| const auto minSize = port.get (uris.mLV2_RESIZE_PORT__minimumSize.get()); | |||
| if (minSize != nullptr) | |||
| atomPort.ensureSizeInBytes ((size_t) lilv_node_as_int (minSize.get())); | |||
| } | |||
| return { std::move (value), successful }; | |||
| } | |||
| class Plugins | |||
| { | |||
| public: | |||
| @@ -1981,6 +1883,106 @@ private: | |||
| std::unique_ptr<LilvWorld, Free> world; | |||
| }; | |||
| class Ports | |||
| { | |||
| public: | |||
| static constexpr auto sequenceSize = 8192; | |||
| template <typename Callback> | |||
| void forEachPort (Callback&& callback) const | |||
| { | |||
| for (const auto& port : controlPorts) | |||
| callback (port.header); | |||
| for (const auto& port : cvPorts) | |||
| callback (port.header); | |||
| for (const auto& port : audioPorts) | |||
| callback (port.header); | |||
| for (const auto& port : atomPorts) | |||
| callback (port.header); | |||
| } | |||
| auto getControlPorts() { return makeSimpleSpan (controlPorts); } | |||
| auto getControlPorts() const { return makeSimpleSpan (controlPorts); } | |||
| auto getCvPorts() { return makeSimpleSpan (cvPorts); } | |||
| auto getCvPorts() const { return makeSimpleSpan (cvPorts); } | |||
| auto getAudioPorts() { return makeSimpleSpan (audioPorts); } | |||
| auto getAudioPorts() const { return makeSimpleSpan (audioPorts); } | |||
| auto getAtomPorts() { return makeSimpleSpan (atomPorts); } | |||
| auto getAtomPorts() const { return makeSimpleSpan (atomPorts); } | |||
| static Optional<Ports> getPorts (World& world, const UsefulUris& uris, const Plugin& plugin, SymbolMap& symap) | |||
| { | |||
| Ports value; | |||
| bool successful = true; | |||
| const auto numPorts = plugin.getNumPorts(); | |||
| const auto timeNode = world.newUri (LV2_TIME__Position); | |||
| for (uint32_t i = 0; i != numPorts; ++i) | |||
| { | |||
| const auto port = plugin.getPortByIndex (i); | |||
| const PortHeader header { String::fromUTF8 (port.getName().getTyped()), | |||
| String::fromUTF8 (port.getSymbol().getTyped()), | |||
| i, | |||
| port.getDirection (uris) }; | |||
| switch (port.getKind (uris)) | |||
| { | |||
| case Port::Kind::control: | |||
| { | |||
| value.controlPorts.push_back ({ header, ParameterInfo::getInfoForPort (uris, port) }); | |||
| break; | |||
| } | |||
| case Port::Kind::cv: | |||
| value.cvPorts.push_back ({ header }); | |||
| break; | |||
| case Port::Kind::audio: | |||
| { | |||
| value.audioPorts.push_back ({ header }); | |||
| break; | |||
| } | |||
| case Port::Kind::atom: | |||
| { | |||
| const auto supportsTime = port.supportsEvent (timeNode.get()); | |||
| value.atomPorts.push_back ({ header, | |||
| (size_t) Ports::sequenceSize, | |||
| symap, | |||
| supportsTime ? SupportsTime::yes : SupportsTime::no }); | |||
| break; | |||
| } | |||
| case Port::Kind::unknown: | |||
| successful = false; | |||
| break; | |||
| } | |||
| } | |||
| for (auto& atomPort : value.atomPorts) | |||
| { | |||
| const auto port = plugin.getPortByIndex (atomPort.header.index); | |||
| const auto minSize = port.get (uris.mLV2_RESIZE_PORT__minimumSize.get()); | |||
| if (minSize != nullptr) | |||
| atomPort.ensureSizeInBytes ((size_t) lilv_node_as_int (minSize.get())); | |||
| } | |||
| return successful ? makeOptional (std::move (value)) : nullopt; | |||
| } | |||
| private: | |||
| std::vector<ControlPort> controlPorts; | |||
| std::vector<CVPort> cvPorts; | |||
| std::vector<AudioPort> audioPorts; | |||
| std::vector<AtomPort> atomPorts; | |||
| }; | |||
| class InstanceWithSupports : private FeaturesDataListener, | |||
| private HandleHolder | |||
| { | |||
| @@ -4753,6 +4755,72 @@ private: | |||
| instance->instance.connectPort (port.header.index, port.data()); | |||
| } | |||
| void writeTimeInfoToPort (AtomPort& port) | |||
| { | |||
| if (port.header.direction != Port::Direction::input || ! port.getSupportsTime()) | |||
| return; | |||
| auto* forge = port.getForge().get(); | |||
| auto* playhead = getPlayHead(); | |||
| if (playhead == nullptr) | |||
| return; | |||
| // Write timing info to the control port | |||
| const auto info = playhead->getPosition(); | |||
| if (! info.hasValue()) | |||
| return; | |||
| const auto& urids = instance->urids; | |||
| lv2_atom_forge_frame_time (forge, 0); | |||
| lv2_shared::ObjectFrame object { forge, (uint32_t) 0, urids.mLV2_TIME__Position }; | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__speed); | |||
| lv2_atom_forge_float (forge, info->getIsPlaying() ? 1.0f : 0.0f); | |||
| if (const auto samples = info->getTimeInSamples()) | |||
| { | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__frame); | |||
| lv2_atom_forge_long (forge, *samples); | |||
| } | |||
| if (const auto bar = info->getBarCount()) | |||
| { | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__bar); | |||
| lv2_atom_forge_long (forge, *bar); | |||
| } | |||
| if (const auto beat = info->getPpqPosition()) | |||
| { | |||
| if (const auto barStart = info->getPpqPositionOfLastBarStart()) | |||
| { | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__barBeat); | |||
| lv2_atom_forge_float (forge, (float) (*beat - *barStart)); | |||
| } | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__beat); | |||
| lv2_atom_forge_double (forge, *beat); | |||
| } | |||
| if (const auto sig = info->getTimeSignature()) | |||
| { | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__beatUnit); | |||
| lv2_atom_forge_int (forge, sig->denominator); | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerBar); | |||
| lv2_atom_forge_float (forge, (float) sig->numerator); | |||
| } | |||
| if (const auto bpm = info->getBpm()) | |||
| { | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerMinute); | |||
| lv2_atom_forge_float (forge, (float) *bpm); | |||
| } | |||
| } | |||
| void preparePortsForRun (AudioBuffer<float>& audio, MidiBuffer& midiBuffer) | |||
| { | |||
| connectPorts (audio); | |||
| @@ -4775,49 +4843,13 @@ private: | |||
| } | |||
| } | |||
| auto* forge = controlPort != nullptr ? controlPort->getForge().get() | |||
| : nullptr; | |||
| if (forge != nullptr) | |||
| { | |||
| // Write timing info to the control port | |||
| if (auto* playhead = getPlayHead()) | |||
| { | |||
| AudioPlayHead::CurrentPositionInfo info; | |||
| if (playhead->getCurrentPosition (info)) | |||
| { | |||
| const auto& urids = instance->urids; | |||
| lv2_atom_forge_frame_time (forge, 0); | |||
| lv2_shared::ObjectFrame object { forge, (uint32_t) 0, urids.mLV2_TIME__Position }; | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__frame); | |||
| lv2_atom_forge_long (forge, info.timeInSamples); | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__speed); | |||
| lv2_atom_forge_float (forge, info.isPlaying ? 1.0f : 0.0f); | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__barBeat); | |||
| lv2_atom_forge_float (forge, (float) (info.ppqPosition - info.ppqPositionOfLastBarStart)); | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__beat); | |||
| lv2_atom_forge_double (forge, info.ppqPosition); | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__beatUnit); | |||
| lv2_atom_forge_int (forge, info.timeSigDenominator); | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerBar); | |||
| lv2_atom_forge_float (forge, (float) info.timeSigNumerator); | |||
| for (auto& port : instance->ports.getAtomPorts()) | |||
| writeTimeInfoToPort (port); | |||
| lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerMinute); | |||
| lv2_atom_forge_float (forge, (float) info.bpm); | |||
| } | |||
| } | |||
| } | |||
| const auto controlPortForge = controlPort != nullptr ? controlPort->getForge().get() | |||
| : nullptr; | |||
| parameterValues.postChangedParametersToProcessor (getParameterWriterUrids(), forge); | |||
| parameterValues.postChangedParametersToProcessor (getParameterWriterUrids(), controlPortForge); | |||
| instance->uiToProcessor.readAllAndClear ([this] (MessageHeader header, uint32_t size, const void* buffer) | |||
| { | |||
| @@ -5364,15 +5396,15 @@ public: | |||
| return lv2_host::PluginState { lilv_state_new_from_world (world->get(), &map, plugin.getUri().get()) }; | |||
| }(); | |||
| auto ports = lv2_host::getPorts (uris, plugin, *symap); | |||
| auto ports = lv2_host::Ports::getPorts (*world, uris, plugin, *symap); | |||
| if (! ports.successful) | |||
| if (! ports.hasValue()) | |||
| return callback (nullptr, "Plugin has ports of an unsupported type"); | |||
| auto instance = std::make_unique<lv2_host::InstanceWithSupports> (*world, | |||
| std::move (symap), | |||
| plugin, | |||
| std::move (ports.value), | |||
| std::move (*ports), | |||
| (int32_t) initialBufferSize, | |||
| initialSampleRate); | |||
| @@ -29,6 +29,8 @@ | |||
| #pragma once | |||
| #ifndef DOXYGEN | |||
| #include <vector> | |||
| namespace juce | |||
| @@ -10229,3 +10231,5 @@ to an instance of LV2_Extension_Data_Feature. | |||
| }; | |||
| } | |||
| #endif | |||
| @@ -27,8 +27,6 @@ | |||
| #ifndef DOXYGEN | |||
| #include <juce_core/containers/juce_Optional.h> | |||
| namespace juce | |||
| { | |||
| @@ -497,27 +495,20 @@ inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::Speak | |||
| */ | |||
| struct ChannelMapping | |||
| { | |||
| explicit ChannelMapping (const AudioChannelSet& layout) | |||
| : ChannelMapping (layout, true) | |||
| { | |||
| } | |||
| ChannelMapping (const AudioChannelSet& layout, bool activeIn) | |||
| : indices (makeChannelIndices (layout)), | |||
| active (activeIn) | |||
| { | |||
| } | |||
| : indices (makeChannelIndices (layout)), active (activeIn) {} | |||
| explicit ChannelMapping (const AudioProcessor::Bus& juceBus) | |||
| : ChannelMapping (juceBus.getLastEnabledLayout(), juceBus.isEnabled()) | |||
| { | |||
| } | |||
| explicit ChannelMapping (const AudioChannelSet& layout) | |||
| : ChannelMapping (layout, true) {} | |||
| explicit ChannelMapping (const AudioProcessor::Bus& bus) | |||
| : ChannelMapping (bus.getLastEnabledLayout(), bus.isEnabled()) {} | |||
| int getJuceChannelForVst3Channel (int vst3Channel) const { return indices[(size_t) vst3Channel]; } | |||
| size_t size() const { return indices.size(); } | |||
| void setActive (bool activeIn) { active = activeIn; } | |||
| void setActive (bool x) { active = x; } | |||
| bool isActive() const { return active; } | |||
| private: | |||
| @@ -545,24 +536,106 @@ private: | |||
| bool active = true; | |||
| }; | |||
| class DynamicChannelMapping | |||
| { | |||
| public: | |||
| DynamicChannelMapping (const AudioChannelSet& channelSet, bool active) | |||
| : set (channelSet), map (channelSet, active) {} | |||
| explicit DynamicChannelMapping (const AudioChannelSet& channelSet) | |||
| : DynamicChannelMapping (channelSet, true) {} | |||
| explicit DynamicChannelMapping (const AudioProcessor::Bus& bus) | |||
| : DynamicChannelMapping (bus.getLastEnabledLayout(), bus.isEnabled()) {} | |||
| AudioChannelSet getAudioChannelSet() const { return set; } | |||
| int getJuceChannelForVst3Channel (int vst3Channel) const { return map.getJuceChannelForVst3Channel (vst3Channel); } | |||
| size_t size() const { return map.size(); } | |||
| /* Returns true if the host has activated this bus. */ | |||
| bool isHostActive() const { return hostActive; } | |||
| /* Returns true if the AudioProcessor expects this bus to be active. */ | |||
| bool isClientActive() const { return map.isActive(); } | |||
| void setHostActive (bool active) { hostActive = active; } | |||
| void setClientActive (bool active) { map.setActive (active); } | |||
| private: | |||
| AudioChannelSet set; | |||
| ChannelMapping map; | |||
| bool hostActive = false; | |||
| }; | |||
| //============================================================================== | |||
| inline auto& getAudioBusPointer (detail::Tag<float>, Steinberg::Vst::AudioBusBuffers& data) { return data.channelBuffers32; } | |||
| inline auto& getAudioBusPointer (detail::Tag<double>, Steinberg::Vst::AudioBusBuffers& data) { return data.channelBuffers64; } | |||
| static inline int countUsedChannels (const std::vector<ChannelMapping>& inputMap, | |||
| const std::vector<ChannelMapping>& outputMap) | |||
| static inline int countUsedClientChannels (const std::vector<DynamicChannelMapping>& inputMap, | |||
| const std::vector<DynamicChannelMapping>& outputMap) | |||
| { | |||
| const auto countUsedChannelsInVector = [] (const std::vector<ChannelMapping>& map) | |||
| const auto countUsedChannelsInVector = [] (const std::vector<DynamicChannelMapping>& map) | |||
| { | |||
| return std::accumulate (map.begin(), map.end(), 0, [] (auto acc, const auto& item) | |||
| { | |||
| return acc + (item.isActive() ? (int) item.size() : 0); | |||
| return acc + (item.isClientActive() ? (int) item.size() : 0); | |||
| }); | |||
| }; | |||
| return jmax (countUsedChannelsInVector (inputMap), countUsedChannelsInVector (outputMap)); | |||
| } | |||
| template <typename FloatType> | |||
| class ScratchBuffer | |||
| { | |||
| public: | |||
| void setSize (int numChannels, int blockSize) | |||
| { | |||
| buffer.setSize (numChannels, blockSize); | |||
| } | |||
| void clear() { channelCounter = 0; } | |||
| auto* getNextChannelBuffer() { return buffer.getWritePointer (channelCounter++); } | |||
| auto getArrayOfWritePointers() { return buffer.getArrayOfWritePointers(); } | |||
| private: | |||
| AudioBuffer<FloatType> buffer; | |||
| int channelCounter = 0; | |||
| }; | |||
| template <typename FloatType> | |||
| static int countValidBuses (Steinberg::Vst::AudioBusBuffers* buffers, int32 num) | |||
| { | |||
| return (int) std::distance (buffers, std::find_if (buffers, buffers + num, [] (auto& buf) | |||
| { | |||
| return getAudioBusPointer (detail::Tag<FloatType>{}, buf) == nullptr && buf.numChannels > 0; | |||
| })); | |||
| } | |||
| template <typename FloatType, typename Iterator> | |||
| static bool validateLayouts (Iterator first, Iterator last, const std::vector<DynamicChannelMapping>& map) | |||
| { | |||
| if ((size_t) std::distance (first, last) > map.size()) | |||
| return false; | |||
| auto mapIterator = map.begin(); | |||
| for (auto it = first; it != last; ++it, ++mapIterator) | |||
| { | |||
| auto** busPtr = getAudioBusPointer (detail::Tag<FloatType>{}, *it); | |||
| const auto anyChannelIsNull = std::any_of (busPtr, busPtr + it->numChannels, [] (auto* ptr) { return ptr == nullptr; }); | |||
| // Null channels are allowed if the bus is inactive | |||
| if ((mapIterator->isHostActive() && anyChannelIsNull) || ((int) mapIterator->size() != it->numChannels)) | |||
| return false; | |||
| } | |||
| // If the host didn't provide the full complement of buses, it must be because the other | |||
| // buses are all deactivated. | |||
| return std::none_of (mapIterator, map.end(), [] (const auto& item) { return item.isHostActive(); }); | |||
| } | |||
| /* | |||
| The main purpose of this class is to remap a set of buffers provided by the VST3 host into an | |||
| equivalent JUCE AudioBuffer using the JUCE channel layout/order. | |||
| @@ -580,154 +653,110 @@ class ClientBufferMapperData | |||
| public: | |||
| void prepare (int numChannels, int blockSize) | |||
| { | |||
| emptyBuffer.setSize (numChannels, blockSize); | |||
| scratchBuffer.setSize (numChannels, blockSize); | |||
| channels.reserve ((size_t) jmin (128, numChannels)); | |||
| } | |||
| AudioBuffer<FloatType> getMappedBuffer (Steinberg::Vst::ProcessData& data, | |||
| const std::vector<ChannelMapping>& inputMap, | |||
| const std::vector<ChannelMapping>& outputMap) | |||
| const std::vector<DynamicChannelMapping>& inputMap, | |||
| const std::vector<DynamicChannelMapping>& outputMap) | |||
| { | |||
| const auto usedChannels = countUsedChannels (inputMap, outputMap); | |||
| scratchBuffer.clear(); | |||
| channels.clear(); | |||
| const auto usedChannels = countUsedClientChannels (inputMap, outputMap); | |||
| // WaveLab workaround: This host may report the wrong number of inputs/outputs so re-count here | |||
| const auto countValidBuses = [] (Steinberg::Vst::AudioBusBuffers* buffers, int32 num) | |||
| { | |||
| return int (std::distance (buffers, std::find_if (buffers, buffers + num, [] (Steinberg::Vst::AudioBusBuffers& buf) | |||
| { | |||
| return getAudioBusPointer (detail::Tag<FloatType>{}, buf) == nullptr && buf.numChannels > 0; | |||
| }))); | |||
| }; | |||
| const auto vstInputs = countValidBuses<FloatType> (data.inputs, data.numInputs); | |||
| const auto vstInputs = countValidBuses (data.inputs, data.numInputs); | |||
| const auto vstOutputs = countValidBuses (data.outputs, data.numOutputs); | |||
| if (! validateLayouts<FloatType> (data.inputs, data.inputs + vstInputs, inputMap)) | |||
| return getBlankBuffer (usedChannels, (int) data.numSamples); | |||
| if (! validateLayouts (data, vstInputs, inputMap, vstOutputs, outputMap)) | |||
| return clearOutputBuffersAndReturnBlankBuffer (data, vstOutputs, usedChannels); | |||
| setUpInputChannels (data, (size_t) vstInputs, scratchBuffer, inputMap, channels); | |||
| setUpOutputChannels (scratchBuffer, outputMap, channels); | |||
| // If we're here, then we know that the host has given us a usable layout | |||
| channels.clear(); | |||
| const auto channelPtr = channels.empty() ? scratchBuffer.getArrayOfWritePointers() | |||
| : channels.data(); | |||
| // Put the host-supplied output channel pointers into JUCE order | |||
| for (size_t i = 0; i < (size_t) vstOutputs; ++i) | |||
| { | |||
| const auto bus = getMappedOutputBus (data, outputMap, i); | |||
| channels.insert (channels.end(), bus.begin(), bus.end()); | |||
| } | |||
| return { channelPtr, (int) channels.size(), (int) data.numSamples }; | |||
| } | |||
| // For input channels that are < the total number of outputs channels, copy the input over | |||
| // the output buffer, at the appropriate JUCE channel index. | |||
| // For input channels that are >= the total number of output channels, add the input buffer | |||
| // pointer to the array of channel pointers. | |||
| for (size_t inputBus = 0, initialBusIndex = 0; inputBus < (size_t) vstInputs; ++inputBus) | |||
| private: | |||
| static void setUpInputChannels (Steinberg::Vst::ProcessData& data, | |||
| size_t vstInputs, | |||
| ScratchBuffer<FloatType>& scratchBuffer, | |||
| const std::vector<DynamicChannelMapping>& map, | |||
| std::vector<FloatType*>& channels) | |||
| { | |||
| for (size_t busIndex = 0; busIndex < map.size(); ++busIndex) | |||
| { | |||
| const auto& map = inputMap[inputBus]; | |||
| const auto mapping = map[busIndex]; | |||
| if (! map.isActive()) | |||
| if (! mapping.isClientActive()) | |||
| continue; | |||
| auto** busPtr = getAudioBusPointer (detail::Tag<FloatType>{}, data.inputs[inputBus]); | |||
| const auto originalSize = channels.size(); | |||
| for (auto i = 0; i < (int) map.size(); ++i) | |||
| { | |||
| const auto destIndex = initialBusIndex + (size_t) map.getJuceChannelForVst3Channel (i); | |||
| for (size_t channelIndex = 0; channelIndex < mapping.size(); ++channelIndex) | |||
| channels.push_back (scratchBuffer.getNextChannelBuffer()); | |||
| channels.resize (jmax (channels.size(), destIndex + 1), nullptr); | |||
| if (auto* dest = channels[destIndex]) | |||
| FloatVectorOperations::copy (dest, busPtr[i], (int) data.numSamples); | |||
| else | |||
| channels[destIndex] = busPtr[i]; | |||
| if (mapping.isHostActive() && busIndex < vstInputs) | |||
| { | |||
| auto** busPtr = getAudioBusPointer (detail::Tag<FloatType>{}, data.inputs[busIndex]); | |||
| for (size_t channelIndex = 0; channelIndex < mapping.size(); ++channelIndex) | |||
| { | |||
| FloatVectorOperations::copy (channels[(size_t) mapping.getJuceChannelForVst3Channel ((int) channelIndex) + originalSize], | |||
| busPtr[channelIndex], | |||
| (size_t) data.numSamples); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (size_t channelIndex = 0; channelIndex < mapping.size(); ++channelIndex) | |||
| FloatVectorOperations::clear (channels[originalSize + channelIndex], (size_t) data.numSamples); | |||
| } | |||
| initialBusIndex += map.size(); | |||
| } | |||
| return { channels.data(), (int) channels.size(), (int) data.numSamples }; | |||
| } | |||
| private: | |||
| AudioBuffer<FloatType> clearOutputBuffersAndReturnBlankBuffer (Steinberg::Vst::ProcessData& data, int vstOutputs, int usedChannels) | |||
| static void setUpOutputChannels (ScratchBuffer<FloatType>& scratchBuffer, | |||
| const std::vector<DynamicChannelMapping>& map, | |||
| std::vector<FloatType*>& channels) | |||
| { | |||
| // The host is ignoring the bus layout we requested, so we can't process sensibly! | |||
| jassertfalse; | |||
| // Clear all output channels | |||
| std::for_each (data.outputs, data.outputs + vstOutputs, [&data] (auto& bus) | |||
| for (size_t i = 0, initialBusIndex = 0; i < (size_t) map.size(); ++i) | |||
| { | |||
| auto** busPtr = getAudioBusPointer (detail::Tag<FloatType>{}, bus); | |||
| std::for_each (busPtr, busPtr + bus.numChannels, [&data] (auto* ptr) | |||
| { | |||
| if (ptr != nullptr) | |||
| FloatVectorOperations::clear (ptr, (int) data.numSamples); | |||
| }); | |||
| }); | |||
| // Return a silent buffer for the AudioProcessor to process | |||
| emptyBuffer.clear(); | |||
| return { emptyBuffer.getArrayOfWritePointers(), | |||
| jmin (emptyBuffer.getNumChannels(), usedChannels), | |||
| data.numSamples }; | |||
| } | |||
| std::vector<FloatType*> getMappedOutputBus (Steinberg::Vst::ProcessData& data, | |||
| const std::vector<ChannelMapping>& maps, | |||
| size_t index) const | |||
| { | |||
| const auto& map = maps[index]; | |||
| if (! map.isActive()) | |||
| return {}; | |||
| auto** busPtr = getAudioBusPointer (detail::Tag<FloatType>{}, data.outputs[index]); | |||
| std::vector<FloatType*> result (map.size(), nullptr); | |||
| const auto& mapping = map[i]; | |||
| for (auto i = 0; i < (int) map.size(); ++i) | |||
| result[(size_t) map.getJuceChannelForVst3Channel (i)] = busPtr[i]; | |||
| if (mapping.isClientActive()) | |||
| { | |||
| for (size_t j = 0; j < mapping.size(); ++j) | |||
| { | |||
| if (channels.size() <= initialBusIndex + j) | |||
| channels.push_back (scratchBuffer.getNextChannelBuffer()); | |||
| } | |||
| return result; | |||
| initialBusIndex += mapping.size(); | |||
| } | |||
| } | |||
| } | |||
| template <typename Iterator> | |||
| static bool validateLayouts (Iterator first, Iterator last, const std::vector<ChannelMapping>& map) | |||
| AudioBuffer<FloatType> getBlankBuffer (int usedChannels, int usedSamples) | |||
| { | |||
| if ((size_t) std::distance (first, last) > map.size()) | |||
| return false; | |||
| auto mapIterator = map.begin(); | |||
| // The host is ignoring the bus layout we requested, so we can't process sensibly! | |||
| jassertfalse; | |||
| for (auto it = first; it != last; ++it, ++mapIterator) | |||
| // Return a silent buffer for the AudioProcessor to process | |||
| for (auto i = 0; i < usedChannels; ++i) | |||
| { | |||
| auto** busPtr = getAudioBusPointer (detail::Tag<FloatType>{}, *it); | |||
| const auto anyChannelIsNull = std::any_of (busPtr, busPtr + it->numChannels, [] (auto* ptr) { return ptr == nullptr; }); | |||
| if (anyChannelIsNull || ((int) mapIterator->size() != it->numChannels)) | |||
| return false; | |||
| channels.push_back (scratchBuffer.getNextChannelBuffer()); | |||
| FloatVectorOperations::clear (channels.back(), usedSamples); | |||
| } | |||
| // If the host didn't provide the full complement of buses, it must be because the other | |||
| // buses are all deactivated. | |||
| return std::none_of (mapIterator, map.end(), [] (const auto& item) { return item.isActive(); }); | |||
| } | |||
| static bool validateLayouts (Steinberg::Vst::ProcessData& data, | |||
| int numInputs, | |||
| const std::vector<ChannelMapping>& inputMap, | |||
| int numOutputs, | |||
| const std::vector<ChannelMapping>& outputMap) | |||
| { | |||
| // The plug-in should only process an activated bus. | |||
| // The host could provide fewer busses in the process call if the last busses are not activated. | |||
| return validateLayouts (data.inputs, data.inputs + numInputs, inputMap) | |||
| && validateLayouts (data.outputs, data.outputs + numOutputs, outputMap); | |||
| return { channels.data(), (int) channels.size(), usedSamples }; | |||
| } | |||
| std::vector<FloatType*> channels; | |||
| AudioBuffer<FloatType> emptyBuffer; | |||
| ScratchBuffer<FloatType> scratchBuffer; | |||
| }; | |||
| //============================================================================== | |||
| @@ -749,65 +778,213 @@ private: | |||
| class ClientBufferMapper | |||
| { | |||
| public: | |||
| void prepare (const AudioProcessor& processor, int blockSize) | |||
| void updateFromProcessor (const AudioProcessor& processor) | |||
| { | |||
| struct Pair | |||
| { | |||
| std::vector<ChannelMapping>& map; | |||
| std::vector<DynamicChannelMapping>& map; | |||
| bool isInput; | |||
| }; | |||
| for (const auto& pair : { Pair { inputMap, true }, Pair { outputMap, false } }) | |||
| { | |||
| pair.map.clear(); | |||
| for (auto i = 0; i < processor.getBusCount (pair.isInput); ++i) | |||
| pair.map.emplace_back (*processor.getBus (pair.isInput, i)); | |||
| if (pair.map.empty()) | |||
| { | |||
| for (auto i = 0; i < processor.getBusCount (pair.isInput); ++i) | |||
| pair.map.emplace_back (*processor.getBus (pair.isInput, i)); | |||
| } | |||
| else | |||
| { | |||
| // The number of buses cannot change after creating a VST3 plugin! | |||
| jassert ((size_t) processor.getBusCount (pair.isInput) == pair.map.size()); | |||
| for (size_t i = 0; i < (size_t) processor.getBusCount (pair.isInput); ++i) | |||
| { | |||
| pair.map[i] = [&] | |||
| { | |||
| DynamicChannelMapping replacement { *processor.getBus (pair.isInput, (int) i) }; | |||
| replacement.setHostActive (pair.map[i].isHostActive()); | |||
| return replacement; | |||
| }(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| const auto findMaxNumChannels = [&] (bool isInput) | |||
| void prepare (int blockSize) | |||
| { | |||
| const auto findNumChannelsWhenAllBusesEnabled = [] (const auto& map) | |||
| { | |||
| auto sum = 0; | |||
| for (auto i = 0; i < processor.getBusCount (isInput); ++i) | |||
| sum += processor.getBus (isInput, i)->getLastEnabledLayout().size(); | |||
| return sum; | |||
| return std::accumulate (map.cbegin(), map.cend(), 0, [] (auto acc, const auto& item) | |||
| { | |||
| return acc + (int) item.size(); | |||
| }); | |||
| }; | |||
| const auto numChannels = jmax (findMaxNumChannels (true), findMaxNumChannels (false)); | |||
| const auto numChannels = jmax (findNumChannelsWhenAllBusesEnabled (inputMap), | |||
| findNumChannelsWhenAllBusesEnabled (outputMap)); | |||
| floatData .prepare (numChannels, blockSize); | |||
| doubleData.prepare (numChannels, blockSize); | |||
| } | |||
| void setInputBusActive (size_t bus, bool state) | |||
| void updateActiveClientBuses (const AudioProcessor::BusesLayout& clientBuses) | |||
| { | |||
| if (bus < inputMap.size()) | |||
| inputMap[bus].setActive (state); | |||
| if ( (size_t) clientBuses.inputBuses .size() != inputMap .size() | |||
| || (size_t) clientBuses.outputBuses.size() != outputMap.size()) | |||
| { | |||
| jassertfalse; | |||
| return; | |||
| } | |||
| const auto sync = [] (auto& map, auto& client) | |||
| { | |||
| for (size_t i = 0; i < map.size(); ++i) | |||
| { | |||
| jassert (client[(int) i] == AudioChannelSet::disabled() || client[(int) i] == map[i].getAudioChannelSet()); | |||
| map[i].setClientActive (client[(int) i] != AudioChannelSet::disabled()); | |||
| } | |||
| }; | |||
| sync (inputMap, clientBuses.inputBuses); | |||
| sync (outputMap, clientBuses.outputBuses); | |||
| } | |||
| void setOutputBusActive (size_t bus, bool state) | |||
| void setInputBusHostActive (size_t bus, bool state) { setHostActive (inputMap, bus, state); } | |||
| void setOutputBusHostActive (size_t bus, bool state) { setHostActive (outputMap, bus, state); } | |||
| auto& getData (detail::Tag<float>) { return floatData; } | |||
| auto& getData (detail::Tag<double>) { return doubleData; } | |||
| AudioChannelSet getRequestedLayoutForInputBus (size_t bus) const | |||
| { | |||
| if (bus < outputMap.size()) | |||
| outputMap[bus].setActive (state); | |||
| return getRequestedLayoutForBus (inputMap, bus); | |||
| } | |||
| template <typename FloatType> | |||
| AudioBuffer<FloatType> getJuceLayoutForVst3Buffer (detail::Tag<FloatType>, Steinberg::Vst::ProcessData& data) | |||
| AudioChannelSet getRequestedLayoutForOutputBus (size_t bus) const | |||
| { | |||
| return getData (detail::Tag<FloatType>{}).getMappedBuffer (data, inputMap, outputMap); | |||
| return getRequestedLayoutForBus (outputMap, bus); | |||
| } | |||
| const std::vector<DynamicChannelMapping>& getInputMap() const { return inputMap; } | |||
| const std::vector<DynamicChannelMapping>& getOutputMap() const { return outputMap; } | |||
| private: | |||
| auto& getData (detail::Tag<float>) { return floatData; } | |||
| auto& getData (detail::Tag<double>) { return doubleData; } | |||
| static void setHostActive (std::vector<DynamicChannelMapping>& map, size_t bus, bool state) | |||
| { | |||
| if (bus < map.size()) | |||
| map[bus].setHostActive (state); | |||
| } | |||
| static AudioChannelSet getRequestedLayoutForBus (const std::vector<DynamicChannelMapping>& map, size_t bus) | |||
| { | |||
| if (bus < map.size() && map[bus].isHostActive()) | |||
| return map[bus].getAudioChannelSet(); | |||
| return AudioChannelSet::disabled(); | |||
| } | |||
| ClientBufferMapperData<float> floatData; | |||
| ClientBufferMapperData<double> doubleData; | |||
| std::vector<ChannelMapping> inputMap; | |||
| std::vector<ChannelMapping> outputMap; | |||
| std::vector<DynamicChannelMapping> inputMap; | |||
| std::vector<DynamicChannelMapping> outputMap; | |||
| }; | |||
| //============================================================================== | |||
| /* Holds a buffer in the JUCE channel layout, and a reference to a Vst ProcessData struct, and | |||
| copies each JUCE channel to the appropriate host output channel when this object goes | |||
| out of scope. | |||
| */ | |||
| template <typename FloatType> | |||
| class ClientRemappedBuffer | |||
| { | |||
| public: | |||
| ClientRemappedBuffer (ClientBufferMapperData<FloatType>& mapperData, | |||
| const std::vector<DynamicChannelMapping>* inputMapIn, | |||
| const std::vector<DynamicChannelMapping>* outputMapIn, | |||
| Steinberg::Vst::ProcessData& hostData) | |||
| : buffer (mapperData.getMappedBuffer (hostData, *inputMapIn, *outputMapIn)), | |||
| outputMap (outputMapIn), | |||
| data (hostData) | |||
| {} | |||
| ClientRemappedBuffer (ClientBufferMapper& mapperIn, Steinberg::Vst::ProcessData& hostData) | |||
| : ClientRemappedBuffer (mapperIn.getData (detail::Tag<FloatType>{}), | |||
| &mapperIn.getInputMap(), | |||
| &mapperIn.getOutputMap(), | |||
| hostData) | |||
| {} | |||
| ~ClientRemappedBuffer() | |||
| { | |||
| // WaveLab workaround: This host may report the wrong number of inputs/outputs so re-count here | |||
| const auto vstOutputs = (size_t) countValidBuses<FloatType> (data.outputs, data.numOutputs); | |||
| if (validateLayouts<FloatType> (data.outputs, data.outputs + vstOutputs, *outputMap)) | |||
| copyToHostOutputBuses (vstOutputs); | |||
| else | |||
| clearHostOutputBuses (vstOutputs); | |||
| } | |||
| AudioBuffer<FloatType> buffer; | |||
| private: | |||
| void copyToHostOutputBuses (size_t vstOutputs) const | |||
| { | |||
| for (size_t i = 0, juceBusOffset = 0; i < outputMap->size(); ++i) | |||
| { | |||
| const auto& mapping = (*outputMap)[i]; | |||
| if (mapping.isHostActive() && i < vstOutputs) | |||
| { | |||
| auto& bus = data.outputs[i]; | |||
| if (mapping.isClientActive()) | |||
| { | |||
| for (size_t j = 0; j < mapping.size(); ++j) | |||
| { | |||
| auto* hostChannel = getAudioBusPointer (detail::Tag<FloatType>{}, bus)[j]; | |||
| const auto juceChannel = juceBusOffset + (size_t) mapping.getJuceChannelForVst3Channel ((int) j); | |||
| FloatVectorOperations::copy (hostChannel, buffer.getReadPointer ((int) juceChannel), (size_t) data.numSamples); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (size_t j = 0; j < mapping.size(); ++j) | |||
| { | |||
| auto* hostChannel = getAudioBusPointer (detail::Tag<FloatType>{}, bus)[j]; | |||
| FloatVectorOperations::clear (hostChannel, (size_t) data.numSamples); | |||
| } | |||
| } | |||
| } | |||
| if (mapping.isClientActive()) | |||
| juceBusOffset += mapping.size(); | |||
| } | |||
| } | |||
| void clearHostOutputBuses (size_t vstOutputs) const | |||
| { | |||
| // The host provided us with an unexpected bus layout. | |||
| jassertfalse; | |||
| std::for_each (data.outputs, data.outputs + vstOutputs, [this] (auto& bus) | |||
| { | |||
| auto** busPtr = getAudioBusPointer (detail::Tag<FloatType>{}, bus); | |||
| std::for_each (busPtr, busPtr + bus.numChannels, [this] (auto* ptr) | |||
| { | |||
| if (ptr != nullptr) | |||
| FloatVectorOperations::clear (ptr, (int) data.numSamples); | |||
| }); | |||
| }); | |||
| } | |||
| const std::vector<DynamicChannelMapping>* outputMap = nullptr; | |||
| Steinberg::Vst::ProcessData& data; | |||
| JUCE_DECLARE_NON_COPYABLE (ClientRemappedBuffer) | |||
| JUCE_DECLARE_NON_MOVEABLE (ClientRemappedBuffer) | |||
| }; | |||
| //============================================================================== | |||
| @@ -94,6 +94,12 @@ static int warnOnFailureIfImplemented (int result) noexcept | |||
| #define warnOnFailureIfImplemented(x) x | |||
| #endif | |||
| enum class Direction { input, output }; | |||
| enum class MediaKind { audio, event }; | |||
| static Vst::MediaType toVstType (MediaKind x) { return x == MediaKind::audio ? Vst::kAudio : Vst::kEvent; } | |||
| static Vst::BusDirection toVstType (Direction x) { return x == Direction::input ? Vst::kInput : Vst::kOutput; } | |||
| static std::vector<Vst::ParamID> getAllParamIDs (Vst::IEditController& controller) | |||
| { | |||
| std::vector<Vst::ParamID> result; | |||
| @@ -211,58 +217,53 @@ static void createPluginDescription (PluginDescription& description, | |||
| } | |||
| static int getNumSingleDirectionBusesFor (Vst::IComponent* component, | |||
| bool checkInputs, | |||
| bool checkAudioChannels) | |||
| MediaKind kind, | |||
| Direction direction) | |||
| { | |||
| jassert (component != nullptr); | |||
| return (int) component->getBusCount (checkAudioChannels ? Vst::kAudio : Vst::kEvent, | |||
| checkInputs ? Vst::kInput : Vst::kOutput); | |||
| JUCE_ASSERT_MESSAGE_THREAD | |||
| return (int) component->getBusCount (toVstType (kind), toVstType (direction)); | |||
| } | |||
| /** Gives the total number of channels for a particular type of bus direction and media type */ | |||
| static int getNumSingleDirectionChannelsFor (Vst::IComponent* component, | |||
| bool checkInputs, | |||
| bool checkAudioChannels) | |||
| static int getNumSingleDirectionChannelsFor (Vst::IComponent* component, Direction busDirection) | |||
| { | |||
| jassert (component != nullptr); | |||
| JUCE_ASSERT_MESSAGE_THREAD | |||
| const Vst::BusDirections direction = checkInputs ? Vst::kInput : Vst::kOutput; | |||
| const Vst::MediaTypes mediaType = checkAudioChannels ? Vst::kAudio : Vst::kEvent; | |||
| const Steinberg::int32 numBuses = component->getBusCount (mediaType, direction); | |||
| const auto direction = toVstType (busDirection); | |||
| const Steinberg::int32 numBuses = component->getBusCount (Vst::kAudio, direction); | |||
| int numChannels = 0; | |||
| for (Steinberg::int32 i = numBuses; --i >= 0;) | |||
| { | |||
| Vst::BusInfo busInfo; | |||
| warnOnFailure (component->getBusInfo (mediaType, direction, i, busInfo)); | |||
| warnOnFailure (component->getBusInfo (Vst::kAudio, direction, i, busInfo)); | |||
| numChannels += ((busInfo.flags & Vst::BusInfo::kDefaultActive) != 0 ? (int) busInfo.channelCount : 0); | |||
| } | |||
| return numChannels; | |||
| } | |||
| static void setStateForAllBusesOfType (Vst::IComponent* component, | |||
| bool state, | |||
| bool activateInputs, | |||
| bool activateAudioChannels) | |||
| static void setStateForAllEventBuses (Vst::IComponent* component, | |||
| bool state, | |||
| Direction busDirection) | |||
| { | |||
| jassert (component != nullptr); | |||
| JUCE_ASSERT_MESSAGE_THREAD | |||
| const Vst::BusDirections direction = activateInputs ? Vst::kInput : Vst::kOutput; | |||
| const Vst::MediaTypes mediaType = activateAudioChannels ? Vst::kAudio : Vst::kEvent; | |||
| const Steinberg::int32 numBuses = component->getBusCount (mediaType, direction); | |||
| const auto direction = toVstType (busDirection); | |||
| const Steinberg::int32 numBuses = component->getBusCount (Vst::kEvent, direction); | |||
| for (Steinberg::int32 i = numBuses; --i >= 0;) | |||
| warnOnFailure (component->activateBus (mediaType, direction, i, state)); | |||
| warnOnFailure (component->activateBus (Vst::kEvent, direction, i, state)); | |||
| } | |||
| //============================================================================== | |||
| static void toProcessContext (Vst::ProcessContext& context, | |||
| AudioPlayHead* playHead, | |||
| double sampleRate, | |||
| const uint64_t* hostTimeNs) | |||
| double sampleRate) | |||
| { | |||
| jassert (sampleRate > 0.0); //Must always be valid, as stated by the VST3 SDK | |||
| @@ -270,59 +271,71 @@ static void toProcessContext (Vst::ProcessContext& context, | |||
| zerostruct (context); | |||
| context.sampleRate = sampleRate; | |||
| auto& fr = context.frameRate; | |||
| if (playHead != nullptr) | |||
| const auto position = playHead != nullptr ? playHead->getPosition() | |||
| : nullopt; | |||
| if (position.hasValue()) | |||
| { | |||
| AudioPlayHead::CurrentPositionInfo position; | |||
| playHead->getCurrentPosition (position); | |||
| if (const auto timeInSamples = position->getTimeInSamples()) | |||
| context.projectTimeSamples = *timeInSamples; | |||
| else | |||
| jassertfalse; // The time in samples *must* be valid. | |||
| context.projectTimeSamples = position.timeInSamples; // Must always be valid, as stated by the VST3 SDK | |||
| context.projectTimeMusic = position.ppqPosition; // Does not always need to be valid... | |||
| context.tempo = position.bpm; | |||
| context.timeSigNumerator = position.timeSigNumerator; | |||
| context.timeSigDenominator = position.timeSigDenominator; | |||
| context.barPositionMusic = position.ppqPositionOfLastBarStart; | |||
| context.cycleStartMusic = position.ppqLoopStart; | |||
| context.cycleEndMusic = position.ppqLoopEnd; | |||
| if (const auto tempo = position->getBpm()) | |||
| { | |||
| context.state |= ProcessContext::kTempoValid; | |||
| context.tempo = *tempo; | |||
| } | |||
| context.frameRate.framesPerSecond = (Steinberg::uint32) position.frameRate.getBaseRate(); | |||
| context.frameRate.flags = (Steinberg::uint32) ((position.frameRate.isDrop() ? FrameRate::kDropRate : 0) | |||
| | (position.frameRate.isPullDown() ? FrameRate::kPullDownRate : 0)); | |||
| if (const auto loop = position->getLoopPoints()) | |||
| { | |||
| context.state |= ProcessContext::kCycleValid; | |||
| context.cycleStartMusic = loop->ppqStart; | |||
| context.cycleEndMusic = loop->ppqEnd; | |||
| } | |||
| if (position.isPlaying) context.state |= ProcessContext::kPlaying; | |||
| if (position.isRecording) context.state |= ProcessContext::kRecording; | |||
| if (position.isLooping) context.state |= ProcessContext::kCycleActive; | |||
| } | |||
| else | |||
| { | |||
| context.tempo = 120.0; | |||
| context.timeSigNumerator = 4; | |||
| context.timeSigDenominator = 4; | |||
| fr.framesPerSecond = 30; | |||
| fr.flags = 0; | |||
| } | |||
| if (const auto sig = position->getTimeSignature()) | |||
| { | |||
| context.state |= ProcessContext::kTimeSigValid; | |||
| context.timeSigNumerator = sig->numerator; | |||
| context.timeSigDenominator = sig->denominator; | |||
| } | |||
| if (context.projectTimeMusic >= 0.0) context.state |= ProcessContext::kProjectTimeMusicValid; | |||
| if (context.barPositionMusic >= 0.0) context.state |= ProcessContext::kBarPositionValid; | |||
| if (context.tempo > 0.0) context.state |= ProcessContext::kTempoValid; | |||
| if (context.frameRate.framesPerSecond > 0) context.state |= ProcessContext::kSmpteValid; | |||
| if (const auto pos = position->getPpqPosition()) | |||
| { | |||
| context.state |= ProcessContext::kProjectTimeMusicValid; | |||
| context.projectTimeMusic = *pos; | |||
| } | |||
| if (context.cycleStartMusic >= 0.0 | |||
| && context.cycleEndMusic > 0.0 | |||
| && context.cycleEndMusic > context.cycleStartMusic) | |||
| { | |||
| context.state |= ProcessContext::kCycleValid; | |||
| } | |||
| if (const auto barStart = position->getPpqPositionOfLastBarStart()) | |||
| { | |||
| context.state |= ProcessContext::kBarPositionValid; | |||
| context.barPositionMusic = *barStart; | |||
| } | |||
| if (const auto frameRate = position->getFrameRate()) | |||
| { | |||
| if (const auto offset = position->getEditOriginTime()) | |||
| { | |||
| context.state |= ProcessContext::kSmpteValid; | |||
| context.smpteOffsetSubframes = (Steinberg::int32) (80.0 * *offset * frameRate->getEffectiveRate()); | |||
| context.frameRate.framesPerSecond = (Steinberg::uint32) frameRate->getBaseRate(); | |||
| context.frameRate.flags = (Steinberg::uint32) ((frameRate->isDrop() ? FrameRate::kDropRate : 0) | |||
| | (frameRate->isPullDown() ? FrameRate::kPullDownRate : 0)); | |||
| } | |||
| } | |||
| if (context.timeSigNumerator > 0 && context.timeSigDenominator > 0) | |||
| context.state |= ProcessContext::kTimeSigValid; | |||
| if (const auto hostTime = position->getHostTimeNs()) | |||
| { | |||
| context.state |= ProcessContext::kSystemTimeValid; | |||
| context.systemTime = (int64_t) *hostTime; | |||
| jassert (context.systemTime >= 0); | |||
| } | |||
| if (hostTimeNs != nullptr) | |||
| { | |||
| context.systemTime = (int64_t) *hostTimeNs; | |||
| jassert (context.systemTime >= 0); | |||
| context.state |= ProcessContext::kSystemTimeValid; | |||
| if (position->getIsPlaying()) context.state |= ProcessContext::kPlaying; | |||
| if (position->getIsRecording()) context.state |= ProcessContext::kRecording; | |||
| if (position->getIsLooping()) context.state |= ProcessContext::kCycleActive; | |||
| } | |||
| } | |||
| @@ -881,8 +894,8 @@ struct DescriptionFactory | |||
| { | |||
| if (component->initialize (vst3HostContext->getFUnknown()) == kResultOk) | |||
| { | |||
| auto numInputs = getNumSingleDirectionChannelsFor (component, true, true); | |||
| auto numOutputs = getNumSingleDirectionChannelsFor (component, false, true); | |||
| auto numInputs = getNumSingleDirectionChannelsFor (component, Direction::input); | |||
| auto numOutputs = getNumSingleDirectionChannelsFor (component, Direction::output); | |||
| createPluginDescription (desc, file, companyName, name, | |||
| info, info2.get(), infoW.get(), numInputs, numOutputs); | |||
| @@ -1479,12 +1492,6 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||
| view = nullptr; | |||
| } | |||
| // FIXME | |||
| void* getPlatformSpecificData() override | |||
| { | |||
| return view; | |||
| } | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| Steinberg::tresult PLUGIN_API queryInterface (const Steinberg::TUID queryIid, void** obj) override | |||
| { | |||
| @@ -1819,9 +1826,6 @@ struct VST3ComponentHolder | |||
| terminate(); | |||
| } | |||
| // transfers ownership to the plugin instance! | |||
| AudioPluginInstance* createPluginInstance(); | |||
| bool isIComponentAlsoIEditController() const | |||
| { | |||
| if (component == nullptr) | |||
| @@ -1915,12 +1919,12 @@ struct VST3ComponentHolder | |||
| Vst::BusInfo bus; | |||
| int totalNumInputChannels = 0, totalNumOutputChannels = 0; | |||
| int n = component->getBusCount(Vst::kAudio, Vst::kInput); | |||
| int n = component->getBusCount (Vst::kAudio, Vst::kInput); | |||
| for (int i = 0; i < n; ++i) | |||
| if (component->getBusInfo (Vst::kAudio, Vst::kInput, i, bus) == kResultOk) | |||
| totalNumInputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0); | |||
| n = component->getBusCount(Vst::kAudio, Vst::kOutput); | |||
| n = component->getBusCount (Vst::kAudio, Vst::kOutput); | |||
| for (int i = 0; i < n; ++i) | |||
| if (component->getBusInfo (Vst::kAudio, Vst::kOutput, i, bus) == kResultOk) | |||
| totalNumOutputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0); | |||
| @@ -2307,22 +2311,21 @@ public: | |||
| const Steinberg::int32 vstParamIndex; | |||
| const Steinberg::Vst::ParamID paramID; | |||
| const bool automatable; | |||
| const bool discrete = getNumSteps() != AudioProcessor::getDefaultNumParameterSteps(); | |||
| const int numSteps = [&] | |||
| { | |||
| auto stepCount = getParameterInfo().stepCount; | |||
| return stepCount == 0 ? AudioProcessor::getDefaultNumParameterSteps() | |||
| : stepCount + 1; | |||
| }(); | |||
| const bool discrete = getNumSteps() != AudioProcessor::getDefaultNumParameterSteps(); | |||
| }; | |||
| //============================================================================== | |||
| VST3PluginInstance (VST3ComponentHolder* componentHolder) | |||
| explicit VST3PluginInstance (std::unique_ptr<VST3ComponentHolder> componentHolder) | |||
| : AudioPluginInstance (getBusProperties (componentHolder->component)), | |||
| holder (componentHolder), | |||
| midiInputs (new MidiEventList()), | |||
| midiOutputs (new MidiEventList()) | |||
| holder (std::move (componentHolder)) | |||
| { | |||
| jassert (holder->isComponentInitialised); | |||
| holder->host->setPlugin (this); | |||
| } | |||
| @@ -2787,12 +2790,9 @@ public: | |||
| // call releaseResources first! | |||
| jassert (! isActive); | |||
| bool result = syncBusLayouts (layouts); | |||
| // didn't succeed? Make sure it's back in its original state | |||
| if (! result) | |||
| syncBusLayouts (getBusesLayout()); | |||
| const auto previousLayout = getBusesLayout(); | |||
| const auto result = syncBusLayouts (layouts); | |||
| syncBusLayouts (previousLayout); | |||
| return result; | |||
| } | |||
| @@ -2857,14 +2857,15 @@ public: | |||
| }; | |||
| //============================================================================== | |||
| String getChannelName (int channelIndex, bool forInput, bool forAudioChannel) const | |||
| String getChannelName (int channelIndex, Direction direction) const | |||
| { | |||
| auto numBuses = getNumSingleDirectionBusesFor (holder->component, forInput, forAudioChannel); | |||
| auto numBuses = getNumSingleDirectionBusesFor (holder->component, MediaKind::audio, direction); | |||
| int numCountedChannels = 0; | |||
| for (int i = 0; i < numBuses; ++i) | |||
| { | |||
| auto busInfo = getBusInfo (forInput, forAudioChannel, i); | |||
| auto busInfo = getBusInfo (MediaKind::audio, direction, i); | |||
| numCountedChannels += busInfo.channelCount; | |||
| @@ -2875,25 +2876,25 @@ public: | |||
| return {}; | |||
| } | |||
| const String getInputChannelName (int channelIndex) const override { return getChannelName (channelIndex, true, true); } | |||
| const String getOutputChannelName (int channelIndex) const override { return getChannelName (channelIndex, false, true); } | |||
| const String getInputChannelName (int channelIndex) const override { return getChannelName (channelIndex, Direction::input); } | |||
| const String getOutputChannelName (int channelIndex) const override { return getChannelName (channelIndex, Direction::output); } | |||
| bool isInputChannelStereoPair (int channelIndex) const override | |||
| { | |||
| int busIdx; | |||
| return getOffsetInBusBufferForAbsoluteChannelIndex (true, channelIndex, busIdx) >= 0 | |||
| && getBusInfo (true, true, busIdx).channelCount == 2; | |||
| && getBusInfo (MediaKind::audio, Direction::input, busIdx).channelCount == 2; | |||
| } | |||
| bool isOutputChannelStereoPair (int channelIndex) const override | |||
| { | |||
| int busIdx; | |||
| return getOffsetInBusBufferForAbsoluteChannelIndex (false, channelIndex, busIdx) >= 0 | |||
| && getBusInfo (false, true, busIdx).channelCount == 2; | |||
| && getBusInfo (MediaKind::audio, Direction::output, busIdx).channelCount == 2; | |||
| } | |||
| bool acceptsMidi() const override { return getNumSingleDirectionBusesFor (holder->component, true, false) > 0; } | |||
| bool producesMidi() const override { return getNumSingleDirectionBusesFor (holder->component, false, false) > 0; } | |||
| bool acceptsMidi() const override { return hasMidiInput; } | |||
| bool producesMidi() const override { return hasMidiOutput; } | |||
| //============================================================================== | |||
| AudioProcessorParameter* getBypassParameter() const override { return bypassParam; } | |||
| @@ -3191,9 +3192,11 @@ private: | |||
| CachedParamValues cachedParamValues; | |||
| VSTComSmartPtr<ParameterChanges> inputParameterChanges { new ParameterChanges }; | |||
| VSTComSmartPtr<ParameterChanges> outputParameterChanges { new ParameterChanges }; | |||
| VSTComSmartPtr<MidiEventList> midiInputs, midiOutputs; | |||
| VSTComSmartPtr<MidiEventList> midiInputs { new MidiEventList }, midiOutputs { new MidiEventList }; | |||
| Vst::ProcessContext timingInfo; //< Only use this in processBlock()! | |||
| bool isControllerInitialised = false, isActive = false, lastProcessBlockCallWasBypass = false; | |||
| const bool hasMidiInput = getNumSingleDirectionBusesFor (holder->component, MediaKind::event, Direction::input) > 0, | |||
| hasMidiOutput = getNumSingleDirectionBusesFor (holder->component, MediaKind::event, Direction::output) > 0; | |||
| VST3Parameter* bypassParam = nullptr; | |||
| //============================================================================== | |||
| @@ -3328,8 +3331,8 @@ private: | |||
| void setStateForAllMidiBuses (bool newState) | |||
| { | |||
| setStateForAllBusesOfType (holder->component, newState, true, false); // Activate/deactivate MIDI inputs | |||
| setStateForAllBusesOfType (holder->component, newState, false, false); // Activate/deactivate MIDI outputs | |||
| setStateForAllEventBuses (holder->component, newState, Direction::input); | |||
| setStateForAllEventBuses (holder->component, newState, Direction::output); | |||
| } | |||
| std::vector<ChannelMapping> createChannelMappings (bool isInput) const | |||
| @@ -3395,11 +3398,11 @@ private: | |||
| } | |||
| //============================================================================== | |||
| Vst::BusInfo getBusInfo (bool forInput, bool forAudio, int index = 0) const | |||
| Vst::BusInfo getBusInfo (MediaKind kind, Direction direction, int index = 0) const | |||
| { | |||
| Vst::BusInfo busInfo; | |||
| busInfo.mediaType = forAudio ? Vst::kAudio : Vst::kEvent; | |||
| busInfo.direction = forInput ? Vst::kInput : Vst::kOutput; | |||
| busInfo.mediaType = toVstType (kind); | |||
| busInfo.direction = toVstType (direction); | |||
| busInfo.channelCount = 0; | |||
| holder->component->getBusInfo (busInfo.mediaType, busInfo.direction, | |||
| @@ -3474,7 +3477,7 @@ private: | |||
| void updateTimingInformation (Vst::ProcessData& destination, double processSampleRate) | |||
| { | |||
| toProcessContext (timingInfo, getPlayHead(), processSampleRate, getHostTimeNs()); | |||
| toProcessContext (timingInfo, getPlayHead(), processSampleRate); | |||
| destination.processContext = &timingInfo; | |||
| } | |||
| @@ -3577,17 +3580,6 @@ private: | |||
| JUCE_END_IGNORE_WARNINGS_MSVC | |||
| //============================================================================== | |||
| AudioPluginInstance* VST3ComponentHolder::createPluginInstance() | |||
| { | |||
| if (! initialise()) | |||
| return nullptr; | |||
| auto* plugin = new VST3PluginInstance (this); | |||
| host->setPlugin (plugin); | |||
| return plugin; | |||
| } | |||
| //============================================================================== | |||
| tresult VST3HostContext::beginEdit (Vst::ParamID paramID) | |||
| { | |||
| @@ -3828,38 +3820,48 @@ void VST3PluginFormat::createARAFactoryAsync (const PluginDescription& descripti | |||
| callback ({ ARAFactoryWrapper { ::juce::getARAFactory (pluginFactory, pluginName) }, {} }); | |||
| } | |||
| void VST3PluginFormat::createPluginInstance (const PluginDescription& description, | |||
| double, int, PluginCreationCallback callback) | |||
| static std::unique_ptr<AudioPluginInstance> createVST3Instance (VST3PluginFormat& format, | |||
| const PluginDescription& description) | |||
| { | |||
| std::unique_ptr<VST3PluginInstance> result; | |||
| if (! format.fileMightContainThisPluginType (description.fileOrIdentifier)) | |||
| return nullptr; | |||
| const File file { description.fileOrIdentifier }; | |||
| if (fileMightContainThisPluginType (description.fileOrIdentifier)) | |||
| struct ScopedWorkingDirectory | |||
| { | |||
| File file (description.fileOrIdentifier); | |||
| ~ScopedWorkingDirectory() { previousWorkingDirectory.setAsCurrentWorkingDirectory(); } | |||
| File previousWorkingDirectory = File::getCurrentWorkingDirectory(); | |||
| }; | |||
| auto previousWorkingDirectory = File::getCurrentWorkingDirectory(); | |||
| file.getParentDirectory().setAsCurrentWorkingDirectory(); | |||
| const ScopedWorkingDirectory scope; | |||
| file.getParentDirectory().setAsCurrentWorkingDirectory(); | |||
| if (const VST3ModuleHandle::Ptr module = VST3ModuleHandle::findOrCreateModule (file, description)) | |||
| { | |||
| std::unique_ptr<VST3ComponentHolder> holder (new VST3ComponentHolder (module)); | |||
| const VST3ModuleHandle::Ptr module { VST3ModuleHandle::findOrCreateModule (file, description) }; | |||
| if (holder->initialise()) | |||
| { | |||
| result.reset (new VST3PluginInstance (holder.release())); | |||
| if (module == nullptr) | |||
| return nullptr; | |||
| if (! result->initialise()) | |||
| result.reset(); | |||
| } | |||
| } | |||
| auto holder = std::make_unique<VST3ComponentHolder> (module); | |||
| previousWorkingDirectory.setAsCurrentWorkingDirectory(); | |||
| } | |||
| if (! holder->initialise()) | |||
| return nullptr; | |||
| String errorMsg; | |||
| auto instance = std::make_unique<VST3PluginInstance> (std::move (holder)); | |||
| if (! instance->initialise()) | |||
| return nullptr; | |||
| return instance; | |||
| } | |||
| void VST3PluginFormat::createPluginInstance (const PluginDescription& description, | |||
| double, int, PluginCreationCallback callback) | |||
| { | |||
| auto result = createVST3Instance (*this, description); | |||
| if (result == nullptr) | |||
| errorMsg = TRANS ("Unable to load XXX plug-in file").replace ("XXX", "VST-3"); | |||
| const auto errorMsg = result == nullptr ? TRANS ("Unable to load XXX plug-in file").replace ("XXX", "VST-3") | |||
| : String(); | |||
| callback (std::move (result), errorMsg); | |||
| } | |||
| @@ -43,7 +43,6 @@ public: | |||
| { | |||
| ChannelMapping map (AudioChannelSet::stereo()); | |||
| expect (map.size() == 2); | |||
| expect (map.isActive() == true); | |||
| expect (map.getJuceChannelForVst3Channel (0) == 0); // L -> left | |||
| expect (map.getJuceChannelForVst3Channel (1) == 1); // R -> right | |||
| @@ -53,7 +52,6 @@ public: | |||
| { | |||
| ChannelMapping map (AudioChannelSet::create9point1point6()); | |||
| expect (map.size() == 16); | |||
| expect (map.isActive() == true); | |||
| // VST3 order is: | |||
| // L | |||
| @@ -115,8 +113,8 @@ public: | |||
| ClientBufferMapperData<float> remapper; | |||
| remapper.prepare (2, blockSize * 2); | |||
| const std::vector<ChannelMapping> emptyBuses; | |||
| const std::vector<ChannelMapping> stereoBus { ChannelMapping { AudioChannelSet::stereo() } }; | |||
| const std::vector<DynamicChannelMapping> emptyBuses; | |||
| const std::vector<DynamicChannelMapping> stereoBus { DynamicChannelMapping { AudioChannelSet::stereo() } }; | |||
| TestBuffers testBuffers { blockSize }; | |||
| @@ -127,12 +125,17 @@ public: | |||
| for (const auto& config : { Config { stereoBus, stereoBus }, Config { emptyBuses, stereoBus }, Config { stereoBus, emptyBuses } }) | |||
| { | |||
| testBuffers.init(); | |||
| const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); | |||
| expect (remapped.getNumChannels() == config.getNumChannels()); | |||
| expect (remapped.getNumSamples() == blockSize); | |||
| for (auto i = 0; i < remapped.getNumChannels(); ++i) | |||
| expect (allMatch (remapped, i, 0.0f)); | |||
| { | |||
| const ClientRemappedBuffer<float> scopedBuffer { remapper, &config.ins, &config.outs, data }; | |||
| auto& remapped = scopedBuffer.buffer; | |||
| expect (remapped.getNumChannels() == config.getNumChannels()); | |||
| expect (remapped.getNumSamples() == blockSize); | |||
| for (auto i = 0; i < remapped.getNumChannels(); ++i) | |||
| expect (allMatch (remapped, i, 0.0f)); | |||
| } | |||
| expect (! testBuffers.isClear (0)); | |||
| expect (! testBuffers.isClear (1)); | |||
| @@ -148,10 +151,10 @@ public: | |||
| ClientBufferMapperData<float> remapper; | |||
| remapper.prepare (3, blockSize * 2); | |||
| const std::vector<ChannelMapping> noBus; | |||
| const std::vector<ChannelMapping> oneBus { ChannelMapping { AudioChannelSet::mono() } }; | |||
| const std::vector<ChannelMapping> twoBuses { ChannelMapping { AudioChannelSet::mono() }, | |||
| ChannelMapping { AudioChannelSet::stereo() } }; | |||
| const std::vector<DynamicChannelMapping> noBus; | |||
| const std::vector<DynamicChannelMapping> oneBus { DynamicChannelMapping { AudioChannelSet::mono() } }; | |||
| const std::vector<DynamicChannelMapping> twoBuses { DynamicChannelMapping { AudioChannelSet::mono() }, | |||
| DynamicChannelMapping { AudioChannelSet::stereo() } }; | |||
| TestBuffers testBuffers { blockSize }; | |||
| @@ -166,12 +169,20 @@ public: | |||
| Config { twoBuses, twoBuses } }) | |||
| { | |||
| testBuffers.init(); | |||
| const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); | |||
| expect (remapped.getNumChannels() == config.getNumChannels()); | |||
| expect (remapped.getNumSamples() == blockSize); | |||
| for (auto i = 0; i < remapped.getNumChannels(); ++i) | |||
| expect (allMatch (remapped, i, 0.0f)); | |||
| { | |||
| const ClientRemappedBuffer<float> scopedBuffer { remapper, &config.ins, &config.outs, data }; | |||
| auto& remapped = scopedBuffer.buffer; | |||
| expect (remapped.getNumChannels() == config.getNumChannels()); | |||
| expect (remapped.getNumSamples() == blockSize); | |||
| // The remapped buffer will only be cleared if the host's input layout does not | |||
| // match the client's input layout. | |||
| if (config.ins.size() != 1) | |||
| for (auto i = 0; i < remapped.getNumChannels(); ++i) | |||
| expect (allMatch (remapped, i, 0.0f)); | |||
| } | |||
| expect (! testBuffers.isClear (0)); | |||
| expect (testBuffers.isClear (1)); | |||
| @@ -183,8 +194,8 @@ public: | |||
| ClientBufferMapperData<float> remapper; | |||
| remapper.prepare (3, blockSize * 2); | |||
| const std::vector<ChannelMapping> monoBus { ChannelMapping { AudioChannelSet::mono() } }; | |||
| const std::vector<ChannelMapping> stereoBus { ChannelMapping { AudioChannelSet::stereo() } }; | |||
| const std::vector<DynamicChannelMapping> monoBus { DynamicChannelMapping { AudioChannelSet::mono() } }; | |||
| const std::vector<DynamicChannelMapping> stereoBus { DynamicChannelMapping { AudioChannelSet::stereo() } }; | |||
| TestBuffers testBuffers { blockSize }; | |||
| @@ -197,12 +208,20 @@ public: | |||
| Config { monoBus, monoBus } }) | |||
| { | |||
| testBuffers.init(); | |||
| const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); | |||
| expect (remapped.getNumChannels() == config.getNumChannels()); | |||
| expect (remapped.getNumSamples() == blockSize); | |||
| for (auto i = 0; i < remapped.getNumChannels(); ++i) | |||
| expect (allMatch (remapped, i, 0.0f)); | |||
| { | |||
| const ClientRemappedBuffer<float> scopedBuffer { remapper, &config.ins, &config.outs, data }; | |||
| auto& remapped = scopedBuffer.buffer; | |||
| expect (remapped.getNumChannels() == config.getNumChannels()); | |||
| expect (remapped.getNumSamples() == blockSize); | |||
| // The remapped buffer will only be cleared if the host's input layout does not | |||
| // match the client's input layout. | |||
| if (config.ins.front().size() != 1) | |||
| for (auto i = 0; i < remapped.getNumChannels(); ++i) | |||
| expect (allMatch (remapped, i, 0.0f)); | |||
| } | |||
| expect (! testBuffers.isClear (0)); | |||
| expect (testBuffers.isClear (1)); | |||
| @@ -215,10 +234,10 @@ public: | |||
| ClientBufferMapperData<float> remapper; | |||
| remapper.prepare (20, blockSize * 2); | |||
| const Config config { { ChannelMapping { AudioChannelSet::mono() }, | |||
| ChannelMapping { AudioChannelSet::create5point1() } }, | |||
| { ChannelMapping { AudioChannelSet::stereo() }, | |||
| ChannelMapping { AudioChannelSet::create7point1() } } }; | |||
| const Config config { { DynamicChannelMapping { AudioChannelSet::mono() }, | |||
| DynamicChannelMapping { AudioChannelSet::create5point1() } }, | |||
| { DynamicChannelMapping { AudioChannelSet::stereo() }, | |||
| DynamicChannelMapping { AudioChannelSet::create7point1() } } }; | |||
| TestBuffers testBuffers { blockSize }; | |||
| @@ -228,45 +247,54 @@ public: | |||
| auto data = makeProcessData (blockSize, ins, outs); | |||
| testBuffers.init(); | |||
| const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); | |||
| expect (remapped.getNumChannels() == 10); | |||
| // Data from the input channels is copied to the correct channels of the remapped buffer | |||
| expect (allMatch (remapped, 0, 1.0f)); | |||
| expect (allMatch (remapped, 1, 2.0f)); | |||
| expect (allMatch (remapped, 2, 3.0f)); | |||
| expect (allMatch (remapped, 3, 4.0f)); | |||
| expect (allMatch (remapped, 4, 5.0f)); | |||
| expect (allMatch (remapped, 5, 6.0f)); | |||
| expect (allMatch (remapped, 6, 7.0f)); | |||
| // These channels are output-only, so they keep whatever data was previously on that output channel | |||
| expect (allMatch (remapped, 7, 17.0f)); | |||
| expect (allMatch (remapped, 8, 14.0f)); | |||
| expect (allMatch (remapped, 9, 15.0f)); | |||
| // Channel pointers from the VST3 buffer are used | |||
| expect (remapped.getReadPointer (0) == testBuffers.get (7)); | |||
| expect (remapped.getReadPointer (1) == testBuffers.get (8)); | |||
| expect (remapped.getReadPointer (2) == testBuffers.get (9)); | |||
| expect (remapped.getReadPointer (3) == testBuffers.get (10)); | |||
| expect (remapped.getReadPointer (4) == testBuffers.get (11)); | |||
| expect (remapped.getReadPointer (5) == testBuffers.get (12)); | |||
| expect (remapped.getReadPointer (6) == testBuffers.get (15)); // JUCE surround side -> VST3 surround side | |||
| expect (remapped.getReadPointer (7) == testBuffers.get (16)); // JUCE surround side -> VST3 surround side | |||
| expect (remapped.getReadPointer (8) == testBuffers.get (13)); // JUCE surround rear -> VST3 surround rear | |||
| expect (remapped.getReadPointer (9) == testBuffers.get (14)); // JUCE surround rear -> VST3 surround rear | |||
| { | |||
| ClientRemappedBuffer<float> scopedBuffer { remapper, &config.ins, &config.outs, data }; | |||
| auto& remapped = scopedBuffer.buffer; | |||
| expect (remapped.getNumChannels() == 10); | |||
| // Data from the input channels is copied to the correct channels of the remapped buffer | |||
| expect (allMatch (remapped, 0, 1.0f)); | |||
| expect (allMatch (remapped, 1, 2.0f)); | |||
| expect (allMatch (remapped, 2, 3.0f)); | |||
| expect (allMatch (remapped, 3, 4.0f)); | |||
| expect (allMatch (remapped, 4, 5.0f)); | |||
| expect (allMatch (remapped, 5, 6.0f)); | |||
| expect (allMatch (remapped, 6, 7.0f)); | |||
| // The remaining channels are output-only, so they may contain any data | |||
| // Write some data to the buffer in JUCE layout | |||
| for (auto i = 0; i < remapped.getNumChannels(); ++i) | |||
| { | |||
| auto* ptr = remapped.getWritePointer (i); | |||
| std::fill (ptr, ptr + remapped.getNumSamples(), (float) i); | |||
| } | |||
| } | |||
| // Channels are copied back to the correct output buffer | |||
| expect (channelStartsWithValue (data.outputs[0], 0, 0.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 1, 1.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 0, 2.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 1, 3.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 2, 4.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 3, 5.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 4, 8.0f)); // JUCE surround side -> VST3 surround side | |||
| expect (channelStartsWithValue (data.outputs[1], 5, 9.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 6, 6.0f)); // JUCE surround rear -> VST3 surround rear | |||
| expect (channelStartsWithValue (data.outputs[1], 7, 7.0f)); | |||
| } | |||
| beginTest ("A layout with more input channels than output channels uses input channels directly in remapped buffer"); | |||
| beginTest ("A layout with more input channels than output channels doesn't attempt to output any input channels"); | |||
| { | |||
| ClientBufferMapperData<float> remapper; | |||
| remapper.prepare (15, blockSize * 2); | |||
| const Config config { { ChannelMapping { AudioChannelSet::create7point1point6() }, | |||
| ChannelMapping { AudioChannelSet::mono() } }, | |||
| { ChannelMapping { AudioChannelSet::createLCRS() }, | |||
| ChannelMapping { AudioChannelSet::stereo() } } }; | |||
| const Config config { { DynamicChannelMapping { AudioChannelSet::create7point1point6() }, | |||
| DynamicChannelMapping { AudioChannelSet::mono() } }, | |||
| { DynamicChannelMapping { AudioChannelSet::createLCRS() }, | |||
| DynamicChannelMapping { AudioChannelSet::stereo() } } }; | |||
| TestBuffers testBuffers { blockSize }; | |||
| @@ -276,116 +304,190 @@ public: | |||
| auto data = makeProcessData (blockSize, ins, outs); | |||
| testBuffers.init(); | |||
| const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); | |||
| expect (remapped.getNumChannels() == 15); | |||
| // Data from the input channels is copied to the correct channels of the remapped buffer | |||
| expect (allMatch (remapped, 0, 1.0f)); | |||
| expect (allMatch (remapped, 1, 2.0f)); | |||
| expect (allMatch (remapped, 2, 3.0f)); | |||
| expect (allMatch (remapped, 3, 4.0f)); | |||
| expect (allMatch (remapped, 4, 7.0f)); | |||
| expect (allMatch (remapped, 5, 8.0f)); | |||
| expect (allMatch (remapped, 6, 9.0f)); | |||
| expect (allMatch (remapped, 7, 10.0f)); | |||
| expect (allMatch (remapped, 8, 11.0f)); | |||
| expect (allMatch (remapped, 9, 12.0f)); | |||
| expect (allMatch (remapped, 10, 5.0f)); | |||
| expect (allMatch (remapped, 11, 6.0f)); | |||
| expect (allMatch (remapped, 12, 13.0f)); | |||
| expect (allMatch (remapped, 13, 14.0f)); | |||
| expect (allMatch (remapped, 14, 15.0f)); | |||
| // Use output channel pointers for output channels | |||
| expect (remapped.getReadPointer (0) == testBuffers.get (15)); | |||
| expect (remapped.getReadPointer (1) == testBuffers.get (16)); | |||
| expect (remapped.getReadPointer (2) == testBuffers.get (17)); | |||
| expect (remapped.getReadPointer (3) == testBuffers.get (18)); | |||
| expect (remapped.getReadPointer (4) == testBuffers.get (19)); | |||
| expect (remapped.getReadPointer (5) == testBuffers.get (20)); | |||
| // Use input channel pointers for channels with no corresponding output | |||
| expect (remapped.getReadPointer (6) == testBuffers.get (8)); | |||
| expect (remapped.getReadPointer (7) == testBuffers.get (9)); | |||
| expect (remapped.getReadPointer (8) == testBuffers.get (10)); | |||
| expect (remapped.getReadPointer (9) == testBuffers.get (11)); | |||
| expect (remapped.getReadPointer (10) == testBuffers.get (4)); | |||
| expect (remapped.getReadPointer (11) == testBuffers.get (5)); | |||
| expect (remapped.getReadPointer (12) == testBuffers.get (12)); | |||
| expect (remapped.getReadPointer (13) == testBuffers.get (13)); | |||
| expect (remapped.getReadPointer (14) == testBuffers.get (14)); | |||
| { | |||
| ClientRemappedBuffer<float> scopedBuffer { remapper, &config.ins, &config.outs, data }; | |||
| auto& remapped = scopedBuffer.buffer; | |||
| expect (remapped.getNumChannels() == 15); | |||
| // Data from the input channels is copied to the correct channels of the remapped buffer | |||
| expect (allMatch (remapped, 0, 1.0f)); | |||
| expect (allMatch (remapped, 1, 2.0f)); | |||
| expect (allMatch (remapped, 2, 3.0f)); | |||
| expect (allMatch (remapped, 3, 4.0f)); | |||
| expect (allMatch (remapped, 4, 7.0f)); | |||
| expect (allMatch (remapped, 5, 8.0f)); | |||
| expect (allMatch (remapped, 6, 9.0f)); | |||
| expect (allMatch (remapped, 7, 10.0f)); | |||
| expect (allMatch (remapped, 8, 11.0f)); | |||
| expect (allMatch (remapped, 9, 12.0f)); | |||
| expect (allMatch (remapped, 10, 5.0f)); | |||
| expect (allMatch (remapped, 11, 6.0f)); | |||
| expect (allMatch (remapped, 12, 13.0f)); | |||
| expect (allMatch (remapped, 13, 14.0f)); | |||
| expect (allMatch (remapped, 14, 15.0f)); | |||
| // Write some data to the buffer in JUCE layout | |||
| for (auto i = 0; i < remapped.getNumChannels(); ++i) | |||
| { | |||
| auto* ptr = remapped.getWritePointer (i); | |||
| std::fill (ptr, ptr + remapped.getNumSamples(), (float) i); | |||
| } | |||
| } | |||
| // Channels are copied back to the correct output buffer | |||
| expect (channelStartsWithValue (data.outputs[0], 0, 0.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 1, 1.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 2, 2.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 3, 3.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 0, 4.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 1, 5.0f)); | |||
| } | |||
| beginTest ("Inactive buses are ignored"); | |||
| { | |||
| ClientBufferMapperData<float> remapper; | |||
| remapper.prepare (15, blockSize * 2); | |||
| remapper.prepare (18, blockSize * 2); | |||
| const Config config { { ChannelMapping { AudioChannelSet::create7point1point6() }, | |||
| ChannelMapping { AudioChannelSet::mono(), false }, | |||
| ChannelMapping { AudioChannelSet::quadraphonic() }, | |||
| ChannelMapping { AudioChannelSet::mono(), false } }, | |||
| { ChannelMapping { AudioChannelSet::create5point0(), false }, | |||
| ChannelMapping { AudioChannelSet::createLCRS() }, | |||
| ChannelMapping { AudioChannelSet::stereo() } } }; | |||
| Config config { { DynamicChannelMapping { AudioChannelSet::create7point1point6() }, | |||
| DynamicChannelMapping { AudioChannelSet::mono(), false }, | |||
| DynamicChannelMapping { AudioChannelSet::quadraphonic() }, | |||
| DynamicChannelMapping { AudioChannelSet::mono(), false } }, | |||
| { DynamicChannelMapping { AudioChannelSet::create5point0(), false }, | |||
| DynamicChannelMapping { AudioChannelSet::createLCRS() }, | |||
| DynamicChannelMapping { AudioChannelSet::stereo() } } }; | |||
| config.ins[1].setHostActive (false); | |||
| config.ins[3].setHostActive (false); | |||
| TestBuffers testBuffers { blockSize }; | |||
| // The host doesn't need to provide trailing buses that are inactive | |||
| // The host doesn't need to provide trailing buses that are inactive, as long as the | |||
| // client knows those buses are inactive. | |||
| auto ins = MultiBusBuffers{}.withBus (testBuffers, 14).withBus (testBuffers, 1).withBus (testBuffers, 4); | |||
| auto outs = MultiBusBuffers{}.withBus (testBuffers, 5) .withBus (testBuffers, 4).withBus (testBuffers, 2); | |||
| auto data = makeProcessData (blockSize, ins, outs); | |||
| testBuffers.init(); | |||
| const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); | |||
| expect (remapped.getNumChannels() == 18); | |||
| // Data from the input channels is copied to the correct channels of the remapped buffer | |||
| expect (allMatch (remapped, 0, 1.0f)); | |||
| expect (allMatch (remapped, 1, 2.0f)); | |||
| expect (allMatch (remapped, 2, 3.0f)); | |||
| expect (allMatch (remapped, 3, 4.0f)); | |||
| expect (allMatch (remapped, 4, 7.0f)); | |||
| expect (allMatch (remapped, 5, 8.0f)); | |||
| expect (allMatch (remapped, 6, 9.0f)); | |||
| expect (allMatch (remapped, 7, 10.0f)); | |||
| expect (allMatch (remapped, 8, 11.0f)); | |||
| expect (allMatch (remapped, 9, 12.0f)); | |||
| expect (allMatch (remapped, 10, 5.0f)); | |||
| expect (allMatch (remapped, 11, 6.0f)); | |||
| expect (allMatch (remapped, 12, 13.0f)); | |||
| expect (allMatch (remapped, 13, 14.0f)); | |||
| expect (allMatch (remapped, 14, 16.0f)); | |||
| expect (allMatch (remapped, 15, 17.0f)); | |||
| expect (allMatch (remapped, 16, 18.0f)); | |||
| expect (allMatch (remapped, 17, 19.0f)); | |||
| // Use output channel pointers for output channels | |||
| expect (remapped.getReadPointer (0) == testBuffers.get (24)); | |||
| expect (remapped.getReadPointer (1) == testBuffers.get (25)); | |||
| expect (remapped.getReadPointer (2) == testBuffers.get (26)); | |||
| expect (remapped.getReadPointer (3) == testBuffers.get (27)); | |||
| expect (remapped.getReadPointer (4) == testBuffers.get (28)); | |||
| expect (remapped.getReadPointer (5) == testBuffers.get (29)); | |||
| // Use input channel pointers for channels with no corresponding output | |||
| expect (remapped.getReadPointer (6) == testBuffers.get (8)); | |||
| expect (remapped.getReadPointer (7) == testBuffers.get (9)); | |||
| expect (remapped.getReadPointer (8) == testBuffers.get (10)); | |||
| expect (remapped.getReadPointer (9) == testBuffers.get (11)); | |||
| expect (remapped.getReadPointer (10) == testBuffers.get (4)); | |||
| expect (remapped.getReadPointer (11) == testBuffers.get (5)); | |||
| expect (remapped.getReadPointer (12) == testBuffers.get (12)); | |||
| expect (remapped.getReadPointer (13) == testBuffers.get (13)); | |||
| expect (remapped.getReadPointer (14) == testBuffers.get (15)); | |||
| expect (remapped.getReadPointer (15) == testBuffers.get (16)); | |||
| expect (remapped.getReadPointer (16) == testBuffers.get (17)); | |||
| expect (remapped.getReadPointer (17) == testBuffers.get (18)); | |||
| { | |||
| ClientRemappedBuffer<float> scopedBuffer { remapper, &config.ins, &config.outs, data }; | |||
| auto& remapped = scopedBuffer.buffer; | |||
| expect (remapped.getNumChannels() == 18); | |||
| // Data from the input channels is copied to the correct channels of the remapped buffer | |||
| expect (allMatch (remapped, 0, 1.0f)); | |||
| expect (allMatch (remapped, 1, 2.0f)); | |||
| expect (allMatch (remapped, 2, 3.0f)); | |||
| expect (allMatch (remapped, 3, 4.0f)); | |||
| expect (allMatch (remapped, 4, 7.0f)); | |||
| expect (allMatch (remapped, 5, 8.0f)); | |||
| expect (allMatch (remapped, 6, 9.0f)); | |||
| expect (allMatch (remapped, 7, 10.0f)); | |||
| expect (allMatch (remapped, 8, 11.0f)); | |||
| expect (allMatch (remapped, 9, 12.0f)); | |||
| expect (allMatch (remapped, 10, 5.0f)); | |||
| expect (allMatch (remapped, 11, 6.0f)); | |||
| expect (allMatch (remapped, 12, 13.0f)); | |||
| expect (allMatch (remapped, 13, 14.0f)); | |||
| expect (allMatch (remapped, 14, 16.0f)); | |||
| expect (allMatch (remapped, 15, 17.0f)); | |||
| expect (allMatch (remapped, 16, 18.0f)); | |||
| expect (allMatch (remapped, 17, 19.0f)); | |||
| // Write some data to the buffer in JUCE layout | |||
| for (auto i = 0; i < remapped.getNumChannels(); ++i) | |||
| { | |||
| auto* ptr = remapped.getWritePointer (i); | |||
| std::fill (ptr, ptr + remapped.getNumSamples(), (float) i); | |||
| } | |||
| } | |||
| // All channels on the first output bus should be cleared, because the plugin | |||
| // thinks that this bus is inactive. | |||
| expect (channelStartsWithValue (data.outputs[0], 0, 0.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 1, 0.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 2, 0.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 3, 0.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 4, 0.0f)); | |||
| // Remaining channels should be copied back as normal | |||
| expect (channelStartsWithValue (data.outputs[1], 0, 0.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 1, 1.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 2, 2.0f)); | |||
| expect (channelStartsWithValue (data.outputs[1], 3, 3.0f)); | |||
| expect (channelStartsWithValue (data.outputs[2], 0, 4.0f)); | |||
| expect (channelStartsWithValue (data.outputs[2], 1, 5.0f)); | |||
| } | |||
| beginTest ("Null pointers are allowed on inactive buses provided to clients"); | |||
| { | |||
| ClientBufferMapperData<float> remapper; | |||
| remapper.prepare (8, blockSize * 2); | |||
| const std::vector<ChannelMapping> emptyBuses; | |||
| const std::vector<ChannelMapping> stereoBus { ChannelMapping { AudioChannelSet::stereo() } }; | |||
| Config config { { DynamicChannelMapping { AudioChannelSet::stereo() }, | |||
| DynamicChannelMapping { AudioChannelSet::quadraphonic(), false }, | |||
| DynamicChannelMapping { AudioChannelSet::stereo() } }, | |||
| { DynamicChannelMapping { AudioChannelSet::quadraphonic() }, | |||
| DynamicChannelMapping { AudioChannelSet::stereo(), false }, | |||
| DynamicChannelMapping { AudioChannelSet::quadraphonic() } } }; | |||
| config.ins[1].setHostActive (false); | |||
| config.outs[1].setHostActive (false); | |||
| TestBuffers testBuffers { blockSize }; | |||
| auto ins = MultiBusBuffers{}.withBus (testBuffers, 2).withBus (testBuffers, 4).withBus (testBuffers, 2); | |||
| auto outs = MultiBusBuffers{}.withBus (testBuffers, 4).withBus (testBuffers, 2).withBus (testBuffers, 4); | |||
| auto data = makeProcessData (blockSize, ins, outs); | |||
| for (auto i = 0; i < 4; ++i) | |||
| data.inputs [1].channelBuffers32[i] = nullptr; | |||
| for (auto i = 0; i < 2; ++i) | |||
| data.outputs[1].channelBuffers32[i] = nullptr; | |||
| testBuffers.init(); | |||
| { | |||
| ClientRemappedBuffer<float> scopedBuffer { remapper, &config.ins, &config.outs, data }; | |||
| auto& remapped = scopedBuffer.buffer; | |||
| expect (remapped.getNumChannels() == 8); | |||
| expect (allMatch (remapped, 0, 1.0f)); | |||
| expect (allMatch (remapped, 1, 2.0f)); | |||
| // skip 4 inactive channels | |||
| expect (allMatch (remapped, 2, 7.0f)); | |||
| expect (allMatch (remapped, 3, 8.0f)); | |||
| // Write some data to the buffer in JUCE layout | |||
| for (auto i = 0; i < remapped.getNumChannels(); ++i) | |||
| { | |||
| auto* ptr = remapped.getWritePointer (i); | |||
| std::fill (ptr, ptr + remapped.getNumSamples(), (float) i); | |||
| } | |||
| } | |||
| expect (channelStartsWithValue (data.outputs[0], 0, 0.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 1, 1.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 2, 2.0f)); | |||
| expect (channelStartsWithValue (data.outputs[0], 3, 3.0f)); | |||
| expect (channelStartsWithValue (data.outputs[2], 0, 4.0f)); | |||
| expect (channelStartsWithValue (data.outputs[2], 1, 5.0f)); | |||
| expect (channelStartsWithValue (data.outputs[2], 2, 6.0f)); | |||
| expect (channelStartsWithValue (data.outputs[2], 3, 7.0f)); | |||
| } | |||
| beginTest ("HostBufferMapper reorders channels correctly"); | |||
| @@ -454,9 +556,17 @@ private: | |||
| //============================================================================== | |||
| struct Config | |||
| { | |||
| std::vector<ChannelMapping> ins, outs; | |||
| Config (std::vector<DynamicChannelMapping> i, std::vector<DynamicChannelMapping> o) | |||
| : ins (std::move (i)), outs (std::move (o)) | |||
| { | |||
| for (auto container : { &ins, &outs }) | |||
| for (auto& x : *container) | |||
| x.setHostActive (true); | |||
| } | |||
| std::vector<DynamicChannelMapping> ins, outs; | |||
| int getNumChannels() const { return countUsedChannels (ins, outs); } | |||
| int getNumChannels() const { return countUsedClientChannels (ins, outs); } | |||
| }; | |||
| struct TestBuffers | |||
| @@ -495,6 +605,11 @@ private: | |||
| int numSamples = 0; | |||
| }; | |||
| static bool channelStartsWithValue (Steinberg::Vst::AudioBusBuffers& bus, size_t index, float value) | |||
| { | |||
| return bus.channelBuffers32[index][0] == value; | |||
| } | |||
| static bool allMatch (const AudioBuffer<float>& buf, int index, float value) | |||
| { | |||
| const auto* ptr = buf.getReadPointer (index); | |||
| @@ -71,7 +71,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) | |||
| #endif | |||
| #ifndef JUCE_VST_FALLBACK_HOST_NAME | |||
| #define JUCE_VST_FALLBACK_HOST_NAME "Carla Plugin Host" | |||
| #define JUCE_VST_FALLBACK_HOST_NAME "Juce VST Host" | |||
| #endif | |||
| //============================================================================== | |||
| @@ -2120,7 +2120,7 @@ private: | |||
| if (effect != nullptr && effect->interfaceIdentifier == Vst2::juceVstInterfaceIdentifier) | |||
| { | |||
| jassert (effect->hostSpace2 == 0); | |||
| jassert (effect->effectPointer != 0); | |||
| jassert (effect->effectPointer != nullptr); | |||
| _fpreset(); // some dodgy plugs mess around with this | |||
| } | |||
| @@ -2274,6 +2274,20 @@ private: | |||
| return { nullptr, nullptr }; | |||
| } | |||
| template <typename Member, typename Value> | |||
| void setFromOptional (Member& target, Optional<Value> opt, int32_t flag) | |||
| { | |||
| if (opt.hasValue()) | |||
| { | |||
| target = static_cast<Member> (*opt); | |||
| vstHostTime.flags |= flag; | |||
| } | |||
| else | |||
| { | |||
| vstHostTime.flags &= ~flag; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| template <typename FloatType> | |||
| void processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, | |||
| @@ -2299,34 +2313,32 @@ private: | |||
| { | |||
| if (auto* currentPlayHead = getPlayHead()) | |||
| { | |||
| AudioPlayHead::CurrentPositionInfo position; | |||
| if (currentPlayHead->getCurrentPosition (position)) | |||
| if (const auto position = currentPlayHead->getPosition()) | |||
| { | |||
| vstHostTime.samplePosition = (double) position.timeInSamples; | |||
| vstHostTime.tempoBPM = position.bpm; | |||
| vstHostTime.timeSignatureNumerator = position.timeSigNumerator; | |||
| vstHostTime.timeSignatureDenominator = position.timeSigDenominator; | |||
| vstHostTime.musicalPosition = position.ppqPosition; | |||
| vstHostTime.lastBarPosition = position.ppqPositionOfLastBarStart; | |||
| vstHostTime.flags |= Vst2::vstTimingInfoFlagTempoValid | |||
| | Vst2::vstTimingInfoFlagTimeSignatureValid | |||
| | Vst2::vstTimingInfoFlagMusicalPositionValid | |||
| | Vst2::vstTimingInfoFlagLastBarPositionValid; | |||
| if (const auto* hostTimeNs = getHostTimeNs()) | |||
| if (const auto samplePos = position->getTimeInSamples()) | |||
| vstHostTime.samplePosition = (double) *samplePos; | |||
| else | |||
| jassertfalse; // VST hosts *must* call setTimeInSamples on the audio playhead | |||
| if (auto sig = position->getTimeSignature()) | |||
| { | |||
| vstHostTime.systemTimeNanoseconds = (double) *hostTimeNs; | |||
| vstHostTime.flags |= Vst2::vstTimingInfoFlagNanosecondsValid; | |||
| vstHostTime.flags |= Vst2::vstTimingInfoFlagTimeSignatureValid; | |||
| vstHostTime.timeSignatureNumerator = sig->numerator; | |||
| vstHostTime.timeSignatureDenominator = sig->denominator; | |||
| } | |||
| else | |||
| { | |||
| vstHostTime.flags &= ~Vst2::vstTimingInfoFlagNanosecondsValid; | |||
| vstHostTime.flags &= ~Vst2::vstTimingInfoFlagTimeSignatureValid; | |||
| } | |||
| setFromOptional (vstHostTime.musicalPosition, position->getPpqPosition(), Vst2::vstTimingInfoFlagMusicalPositionValid); | |||
| setFromOptional (vstHostTime.lastBarPosition, position->getPpqPositionOfLastBarStart(), Vst2::vstTimingInfoFlagLastBarPositionValid); | |||
| setFromOptional (vstHostTime.systemTimeNanoseconds, position->getHostTimeNs(), Vst2::vstTimingInfoFlagNanosecondsValid); | |||
| setFromOptional (vstHostTime.tempoBPM, position->getBpm(), Vst2::vstTimingInfoFlagTempoValid); | |||
| int32 newTransportFlags = 0; | |||
| if (position.isPlaying) newTransportFlags |= Vst2::vstTimingInfoFlagCurrentlyPlaying; | |||
| if (position.isRecording) newTransportFlags |= Vst2::vstTimingInfoFlagCurrentlyRecording; | |||
| if (position->getIsPlaying()) newTransportFlags |= Vst2::vstTimingInfoFlagCurrentlyPlaying; | |||
| if (position->getIsRecording()) newTransportFlags |= Vst2::vstTimingInfoFlagCurrentlyRecording; | |||
| if (newTransportFlags != (vstHostTime.flags & (Vst2::vstTimingInfoFlagCurrentlyPlaying | |||
| | Vst2::vstTimingInfoFlagCurrentlyRecording))) | |||
| @@ -2334,15 +2346,18 @@ private: | |||
| else | |||
| vstHostTime.flags &= ~Vst2::vstTimingInfoFlagTransportChanged; | |||
| const auto optionalFrameRate = [&fr = position.frameRate]() -> Optional<int32> | |||
| const auto optionalFrameRate = [fr = position->getFrameRate()]() -> Optional<int32> | |||
| { | |||
| switch (fr.getBaseRate()) | |||
| if (! fr.hasValue()) | |||
| return {}; | |||
| switch (fr->getBaseRate()) | |||
| { | |||
| case 24: return fr.isPullDown() ? Vst2::vstSmpteRateFps239 : Vst2::vstSmpteRateFps24; | |||
| case 25: return fr.isPullDown() ? Vst2::vstSmpteRateFps249 : Vst2::vstSmpteRateFps25; | |||
| case 30: return fr.isPullDown() ? (fr.isDrop() ? Vst2::vstSmpteRateFps2997drop : Vst2::vstSmpteRateFps2997) | |||
| : (fr.isDrop() ? Vst2::vstSmpteRateFps30drop : Vst2::vstSmpteRateFps30); | |||
| case 60: return fr.isPullDown() ? Vst2::vstSmpteRateFps599 : Vst2::vstSmpteRateFps60; | |||
| case 24: return fr->isPullDown() ? Vst2::vstSmpteRateFps239 : Vst2::vstSmpteRateFps24; | |||
| case 25: return fr->isPullDown() ? Vst2::vstSmpteRateFps249 : Vst2::vstSmpteRateFps25; | |||
| case 30: return fr->isPullDown() ? (fr->isDrop() ? Vst2::vstSmpteRateFps2997drop : Vst2::vstSmpteRateFps2997) | |||
| : (fr->isDrop() ? Vst2::vstSmpteRateFps30drop : Vst2::vstSmpteRateFps30); | |||
| case 60: return fr->isPullDown() ? Vst2::vstSmpteRateFps599 : Vst2::vstSmpteRateFps60; | |||
| } | |||
| return {}; | |||
| @@ -2350,18 +2365,24 @@ private: | |||
| vstHostTime.flags |= optionalFrameRate ? Vst2::vstTimingInfoFlagSmpteValid : 0; | |||
| vstHostTime.smpteRate = optionalFrameRate.orFallback (0); | |||
| vstHostTime.smpteOffset = (int32) (position.timeInSeconds * 80.0 * position.frameRate.getEffectiveRate() + 0.5); | |||
| const auto effectiveRate = position->getFrameRate().hasValue() ? position->getFrameRate()->getEffectiveRate() : 0.0; | |||
| vstHostTime.smpteOffset = (int32) (position->getTimeInSeconds().orFallback (0.0) * 80.0 * effectiveRate + 0.5); | |||
| if (position.isLooping) | |||
| if (const auto loop = position->getLoopPoints()) | |||
| { | |||
| vstHostTime.loopStartPosition = position.ppqLoopStart; | |||
| vstHostTime.loopEndPosition = position.ppqLoopEnd; | |||
| vstHostTime.flags |= (Vst2::vstTimingInfoFlagLoopPositionValid | Vst2::vstTimingInfoFlagLoopActive); | |||
| vstHostTime.flags |= Vst2::vstTimingInfoFlagLoopPositionValid; | |||
| vstHostTime.loopStartPosition = loop->ppqStart; | |||
| vstHostTime.loopEndPosition = loop->ppqEnd; | |||
| } | |||
| else | |||
| { | |||
| vstHostTime.flags &= ~(Vst2::vstTimingInfoFlagLoopPositionValid | Vst2::vstTimingInfoFlagLoopActive); | |||
| vstHostTime.flags &= ~Vst2::vstTimingInfoFlagLoopPositionValid; | |||
| } | |||
| if (position->getIsLooping()) | |||
| vstHostTime.flags |= Vst2::vstTimingInfoFlagLoopActive; | |||
| else | |||
| vstHostTime.flags &= ~Vst2::vstTimingInfoFlagLoopActive; | |||
| } | |||
| } | |||
| @@ -42,7 +42,6 @@ | |||
| #include "juce_audio_processors.h" | |||
| #include <juce_gui_extra/juce_gui_extra.h> | |||
| #include <juce_core/containers/juce_Optional.h> | |||
| //============================================================================== | |||
| #if (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3) && (JUCE_LINUX || JUCE_BSD) && ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
| @@ -157,21 +156,19 @@ private: | |||
| } | |||
| } | |||
| struct FlippedNSView : public ObjCClass<NSView> | |||
| struct InnerNSView : public ObjCClass<NSView> | |||
| { | |||
| FlippedNSView() | |||
| : ObjCClass ("JuceFlippedNSView_") | |||
| InnerNSView() | |||
| : ObjCClass ("JuceInnerNSView_") | |||
| { | |||
| addIvar<NSViewComponentWithParent*> ("owner"); | |||
| addMethod (@selector (isFlipped), isFlipped); | |||
| addMethod (@selector (isOpaque), isOpaque); | |||
| addMethod (@selector (didAddSubview:), didAddSubview); | |||
| registerClass(); | |||
| } | |||
| static BOOL isFlipped (id, SEL) { return YES; } | |||
| static BOOL isOpaque (id, SEL) { return YES; } | |||
| static void nudge (id self) | |||
| @@ -181,15 +178,12 @@ private: | |||
| owner->triggerAsyncUpdate(); | |||
| } | |||
| static void viewDidUnhide (id self, SEL) { nudge (self); } | |||
| static void didAddSubview (id self, SEL, NSView*) { nudge (self); } | |||
| static void viewDidMoveToSuperview (id self, SEL) { nudge (self); } | |||
| static void viewDidMoveToWindow (id self, SEL) { nudge (self); } | |||
| }; | |||
| static FlippedNSView& getViewClass() | |||
| static InnerNSView& getViewClass() | |||
| { | |||
| static FlippedNSView result; | |||
| static InnerNSView result; | |||
| return result; | |||
| } | |||
| }; | |||
| @@ -201,46 +195,51 @@ private: | |||
| #include "utilities/juce_FlagCache.h" | |||
| #include "format/juce_AudioPluginFormat.cpp" | |||
| #include "format/juce_AudioPluginFormatManager.cpp" | |||
| // #include "format_types/juce_LegacyAudioParameter.cpp" | |||
| #include "format_types/juce_LegacyAudioParameter.cpp" | |||
| #include "processors/juce_AudioProcessor.cpp" | |||
| #include "processors/juce_AudioPluginInstance.cpp" | |||
| // #include "processors/juce_AudioProcessorGraph.cpp" | |||
| #include "processors/juce_AudioProcessorGraph.cpp" | |||
| #if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
| #include "processors/juce_AudioProcessorEditor.cpp" | |||
| // #include "processors/juce_GenericAudioProcessorEditor.cpp" | |||
| #include "processors/juce_GenericAudioProcessorEditor.cpp" | |||
| #endif | |||
| #include "processors/juce_PluginDescription.cpp" | |||
| // #include "format_types/juce_ARACommon.cpp" | |||
| // #include "format_types/juce_LADSPAPluginFormat.cpp" | |||
| #include "format_types/juce_ARACommon.cpp" | |||
| #include "format_types/juce_LADSPAPluginFormat.cpp" | |||
| #include "format_types/juce_VSTPluginFormat.cpp" | |||
| #include "format_types/juce_VST3PluginFormat.cpp" | |||
| #include "format_types/juce_AudioUnitPluginFormat.mm" | |||
| // #include "format_types/juce_ARAHosting.cpp" | |||
| #include "format_types/juce_ARAHosting.cpp" | |||
| #if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
| #include "scanning/juce_KnownPluginList.cpp" | |||
| // #include "scanning/juce_PluginDirectoryScanner.cpp" | |||
| // #include "scanning/juce_PluginListComponent.cpp" | |||
| #include "scanning/juce_PluginDirectoryScanner.cpp" | |||
| #include "scanning/juce_PluginListComponent.cpp" | |||
| #endif | |||
| #include "processors/juce_AudioProcessorParameterGroup.cpp" | |||
| #include "utilities/juce_AudioProcessorParameterWithID.cpp" | |||
| // #include "utilities/juce_RangedAudioParameter.cpp" | |||
| // #include "utilities/juce_AudioParameterFloat.cpp" | |||
| // #include "utilities/juce_AudioParameterInt.cpp" | |||
| // #include "utilities/juce_AudioParameterBool.cpp" | |||
| // #include "utilities/juce_AudioParameterChoice.cpp" | |||
| // #if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
| // #include "utilities/juce_ParameterAttachments.cpp" | |||
| // #endif | |||
| // #include "utilities/juce_AudioProcessorValueTreeState.cpp" | |||
| // #include "utilities/juce_PluginHostType.cpp" | |||
| #include "utilities/juce_RangedAudioParameter.cpp" | |||
| #include "utilities/juce_AudioParameterFloat.cpp" | |||
| #include "utilities/juce_AudioParameterInt.cpp" | |||
| #include "utilities/juce_AudioParameterBool.cpp" | |||
| #include "utilities/juce_AudioParameterChoice.cpp" | |||
| #if ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
| #include "utilities/juce_ParameterAttachments.cpp" | |||
| #endif | |||
| #include "utilities/juce_AudioProcessorValueTreeState.cpp" | |||
| #include "utilities/juce_PluginHostType.cpp" | |||
| #include "utilities/juce_NativeScaleFactorNotifier.cpp" | |||
| // #include "utilities/ARA/juce_ARA_utils.cpp" | |||
| // | |||
| // #include "format_types/juce_LV2PluginFormat.cpp" | |||
| #include "utilities/ARA/juce_ARA_utils.cpp" | |||
| #include "format_types/juce_LV2PluginFormat.cpp" | |||
| #if JUCE_UNIT_TESTS | |||
| #include "format_types/juce_VST3PluginFormat_test.cpp" | |||
| #include "format_types/juce_LV2PluginFormat_test.cpp" | |||
| #if JUCE_PLUGINHOST_VST3 | |||
| #include "format_types/juce_VST3PluginFormat_test.cpp" | |||
| #endif | |||
| #if JUCE_PLUGINHOST_LV2 | |||
| #include "format_types/juce_LV2PluginFormat_test.cpp" | |||
| #endif | |||
| #endif | |||
| #if JUCE_AUDIOPROCESSOR_NO_GUI | |||
| @@ -35,7 +35,7 @@ | |||
| ID: juce_audio_processors | |||
| vendor: juce | |||
| version: 6.1.6 | |||
| version: 7.0.1 | |||
| name: JUCE audio processor classes | |||
| description: Classes for loading and playing VST, AU, LADSPA, or internally-generated audio processors. | |||
| website: http://www.juce.com/juce | |||
| @@ -1141,11 +1141,6 @@ public: | |||
| /** This method is called when the layout of the audio processor changes. */ | |||
| virtual void processorLayoutsChanged(); | |||
| //============================================================================== | |||
| /** LV2 specific calls, saving/restore as string. */ | |||
| virtual String getStateInformationString () { return String(); } | |||
| virtual void setStateInformationString (const String&) {} | |||
| //============================================================================== | |||
| /** Adds a listener that will be called when an aspect of this processor changes. */ | |||
| virtual void addListener (AudioProcessorListener* newListener); | |||
| @@ -1160,51 +1155,6 @@ public: | |||
| */ | |||
| virtual void setPlayHead (AudioPlayHead* newPlayHead); | |||
| //============================================================================== | |||
| /** Hosts may call this function to supply the system time corresponding to the | |||
| current audio buffer. | |||
| If you want to set a valid time, pass a pointer to a uint64_t holding the current time. The | |||
| value will be copied into the AudioProcessor instance without any allocation/deallocation. | |||
| If you want to clear any stored host time, pass nullptr. | |||
| Calls to this function must be synchronised (i.e. not simultaneous) with the audio callback. | |||
| @code | |||
| const auto currentHostTime = computeHostTimeNanos(); | |||
| processor.setHostTimeNanos (¤tHostTime); // Set a valid host time | |||
| // ...call processBlock etc. | |||
| processor.setHostTimeNanos (nullptr); // Clear host time | |||
| @endcode | |||
| */ | |||
| void setHostTimeNanos (const uint64_t* hostTimeIn) | |||
| { | |||
| hasHostTime = hostTimeIn != nullptr; | |||
| hostTime = hasHostTime ? *hostTimeIn : 0; | |||
| } | |||
| /** The plugin may call this function inside the processBlock function (and only there!) | |||
| to find the timestamp associated with the current audio block. | |||
| If a timestamp is available, this will return a pointer to that timestamp. You should | |||
| immediately copy the pointed-to value and use that in any following code. Do *not* free | |||
| any pointer returned by this function. | |||
| If no timestamp is provided, this will return nullptr. | |||
| @code | |||
| void processBlock (AudioBuffer<float>&, MidiBuffer&) override | |||
| { | |||
| if (auto* timestamp = getHostTimeNs()) | |||
| { | |||
| // Use *timestamp here to compensate for callback jitter etc. | |||
| } | |||
| } | |||
| @endcode | |||
| */ | |||
| const uint64_t* getHostTimeNs() const { return hasHostTime ? &hostTime : nullptr; } | |||
| //============================================================================== | |||
| /** This is called by the processor to specify its details before being played. Use this | |||
| version of the function if you are not interested in any sidechain and/or aux buses | |||
| @@ -1565,9 +1515,6 @@ private: | |||
| AudioProcessorParameterGroup parameterTree; | |||
| Array<AudioProcessorParameter*> flatParameterList; | |||
| uint64_t hostTime = 0; | |||
| bool hasHostTime = false; | |||
| AudioProcessorParameter* getParamChecked (int) const; | |||
| #if JUCE_DEBUG | |||
| @@ -46,9 +46,6 @@ AudioProcessorEditor::~AudioProcessorEditor() | |||
| removeComponentListener (resizeListener.get()); | |||
| } | |||
| // FIXME | |||
| void* AudioProcessorEditor::getPlatformSpecificData() { return nullptr; } | |||
| void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} | |||
| int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } | |||
| @@ -50,9 +50,6 @@ protected: | |||
| AudioProcessorEditor (AudioProcessor*) noexcept; | |||
| public: | |||
| // FIXME | |||
| virtual void* getPlatformSpecificData(); | |||
| /** Destructor. */ | |||
| ~AudioProcessorEditor() override; | |||
| @@ -37,18 +37,15 @@ static void updateOnMessageThread (AsyncUpdater& updater) | |||
| template <typename FloatType> | |||
| struct GraphRenderSequence | |||
| { | |||
| GraphRenderSequence() {} | |||
| struct Context | |||
| { | |||
| FloatType** audioBuffers; | |||
| MidiBuffer* midiBuffers; | |||
| AudioPlayHead* audioPlayHead; | |||
| Optional<uint64_t> hostTimeNs; | |||
| int numSamples; | |||
| }; | |||
| void perform (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead, Optional<uint64_t> hostTimeNs) | |||
| void perform (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead) | |||
| { | |||
| auto numSamples = buffer.getNumSamples(); | |||
| auto maxSamples = renderingBuffer.getNumSamples(); | |||
| @@ -67,7 +64,7 @@ struct GraphRenderSequence | |||
| // Splitting up the buffer like this will cause the play head and host time to be | |||
| // invalid for all but the first chunk... | |||
| perform (audioChunk, midiChunk, audioPlayHead, hostTimeNs); | |||
| perform (audioChunk, midiChunk, audioPlayHead); | |||
| chunkStartSample += maxSamples; | |||
| } | |||
| @@ -82,7 +79,7 @@ struct GraphRenderSequence | |||
| currentMidiOutputBuffer.clear(); | |||
| { | |||
| const Context context { renderingBuffer.getArrayOfWritePointers(), midiBuffers.begin(), audioPlayHead, hostTimeNs, numSamples }; | |||
| const Context context { renderingBuffer.getArrayOfWritePointers(), midiBuffers.begin(), audioPlayHead, numSamples }; | |||
| for (auto* op : renderOps) | |||
| op->perform (context); | |||
| @@ -267,7 +264,6 @@ private: | |||
| void perform (const Context& c) override | |||
| { | |||
| processor.setPlayHead (c.audioPlayHead); | |||
| processor.setHostTimeNanos (c.hostTimeNs.hasValue() ? &(*c.hostTimeNs) : nullptr); | |||
| for (int i = 0; i < totalChans; ++i) | |||
| audioChannels[i] = c.audioBuffers[audioChannelsToUse.getUnchecked (i)]; | |||
| @@ -289,8 +285,6 @@ private: | |||
| buffer.clear(); | |||
| else | |||
| callProcess (buffer, c.midiBuffers[midiBufferToUse]); | |||
| processor.setHostTimeNanos (nullptr); | |||
| } | |||
| void callProcess (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) | |||
| @@ -1402,14 +1396,6 @@ static void processBlockForBuffer (AudioBuffer<FloatType>& buffer, MidiBuffer& m | |||
| std::unique_ptr<SequenceType>& renderSequence, | |||
| std::atomic<bool>& isPrepared) | |||
| { | |||
| const auto getHostTime = [&]() -> Optional<uint64_t> | |||
| { | |||
| if (auto* nanos = graph.getHostTimeNs()) | |||
| return *nanos; | |||
| return nullopt; | |||
| }; | |||
| if (graph.isNonRealtime()) | |||
| { | |||
| while (! isPrepared) | |||
| @@ -1418,7 +1404,7 @@ static void processBlockForBuffer (AudioBuffer<FloatType>& buffer, MidiBuffer& m | |||
| const ScopedLock sl (graph.getCallbackLock()); | |||
| if (renderSequence != nullptr) | |||
| renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), getHostTime()); | |||
| renderSequence->perform (buffer, midiMessages, graph.getPlayHead()); | |||
| } | |||
| else | |||
| { | |||
| @@ -1427,7 +1413,7 @@ static void processBlockForBuffer (AudioBuffer<FloatType>& buffer, MidiBuffer& m | |||
| if (isPrepared) | |||
| { | |||
| if (renderSequence != nullptr) | |||
| renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), getHostTime()); | |||
| renderSequence->perform (buffer, midiMessages, graph.getPlayHead()); | |||
| } | |||
| else | |||
| { | |||
| @@ -335,28 +335,28 @@ protected: | |||
| //============================================================================== | |||
| /** Override to return a custom subclass instance of ARADocument. */ | |||
| ARADocument* doCreateDocument(); | |||
| virtual ARADocument* doCreateDocument(); | |||
| /** Override to return a custom subclass instance of ARAMusicalContext. */ | |||
| ARAMusicalContext* doCreateMusicalContext (ARADocument* document, | |||
| ARA::ARAMusicalContextHostRef hostRef); | |||
| virtual ARAMusicalContext* doCreateMusicalContext (ARADocument* document, | |||
| ARA::ARAMusicalContextHostRef hostRef); | |||
| /** Override to return a custom subclass instance of ARARegionSequence. */ | |||
| ARARegionSequence* doCreateRegionSequence (ARADocument* document, | |||
| ARA::ARARegionSequenceHostRef hostRef); | |||
| virtual ARARegionSequence* doCreateRegionSequence (ARADocument* document, | |||
| ARA::ARARegionSequenceHostRef hostRef); | |||
| /** Override to return a custom subclass instance of ARAAudioSource. */ | |||
| ARAAudioSource* doCreateAudioSource (ARADocument* document, | |||
| ARA::ARAAudioSourceHostRef hostRef); | |||
| virtual ARAAudioSource* doCreateAudioSource (ARADocument* document, | |||
| ARA::ARAAudioSourceHostRef hostRef); | |||
| /** Override to return a custom subclass instance of ARAAudioModification. */ | |||
| ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource, | |||
| ARA::ARAAudioModificationHostRef hostRef, | |||
| const ARAAudioModification* optionalModificationToClone); | |||
| virtual ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource, | |||
| ARA::ARAAudioModificationHostRef hostRef, | |||
| const ARAAudioModification* optionalModificationToClone); | |||
| /** Override to return a custom subclass instance of ARAPlaybackRegion. */ | |||
| ARAPlaybackRegion* doCreatePlaybackRegion (ARAAudioModification* modification, | |||
| ARA::ARAPlaybackRegionHostRef hostRef); | |||
| virtual ARAPlaybackRegion* doCreatePlaybackRegion (ARAAudioModification* modification, | |||
| ARA::ARAPlaybackRegionHostRef hostRef); | |||
| private: | |||
| //============================================================================== | |||
| @@ -458,7 +458,7 @@ public: | |||
| } | |||
| /** Called before the musical context is destroyed. | |||
| @param musicalContext The musical context that will be destoyed. | |||
| @param musicalContext The musical context that will be destroyed. | |||
| */ | |||
| virtual void willDestroyMusicalContext (ARAMusicalContext* musicalContext) | |||
| { | |||
| @@ -559,7 +559,7 @@ public: | |||
| } | |||
| /** Called before the playback region is destroyed. | |||
| @param playbackRegion The playback region that will be destoyed. | |||
| @param playbackRegion The playback region that will be destroyed. | |||
| */ | |||
| virtual void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion) | |||
| { | |||
| @@ -706,7 +706,7 @@ public: | |||
| } | |||
| /** Called before the region sequence is destroyed. | |||
| @param regionSequence The region sequence that will be destoyed. | |||
| @param regionSequence The region sequence that will be destroyed. | |||
| */ | |||
| virtual void willDestroyRegionSequence (ARARegionSequence* regionSequence) | |||
| { | |||
| @@ -898,7 +898,7 @@ public: | |||
| } | |||
| /** Called before the audio source is destroyed. | |||
| @param audioSource The audio source that will be destoyed. | |||
| @param audioSource The audio source that will be destroyed. | |||
| */ | |||
| virtual void willDestroyAudioSource (ARAAudioSource* audioSource) | |||
| { | |||
| @@ -1072,7 +1072,7 @@ public: | |||
| } | |||
| /** Called before the audio modification is destroyed. | |||
| @param audioModification The audio modification that will be destoyed. | |||
| @param audioModification The audio modification that will be destroyed. | |||
| */ | |||
| virtual void willDestroyAudioModification (ARAAudioModification* audioModification) | |||
| { | |||
| @@ -30,7 +30,7 @@ namespace juce | |||
| bool ARARenderer::processBlock (AudioBuffer<double>& buffer, | |||
| AudioProcessor::Realtime realtime, | |||
| const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept | |||
| const AudioPlayHead::PositionInfo& positionInfo) noexcept | |||
| { | |||
| ignoreUnused (buffer, realtime, positionInfo); | |||
| @@ -88,7 +88,7 @@ public: | |||
| */ | |||
| virtual bool processBlock (AudioBuffer<float>& buffer, | |||
| AudioProcessor::Realtime realtime, | |||
| const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept = 0; | |||
| const AudioPlayHead::PositionInfo& positionInfo) noexcept = 0; | |||
| /** Renders the output into the given buffer. Returns true if rendering executed without error, | |||
| false otherwise. | |||
| @@ -108,7 +108,7 @@ public: | |||
| */ | |||
| virtual bool processBlock (AudioBuffer<double>& buffer, | |||
| AudioProcessor::Realtime realtime, | |||
| const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept; | |||
| const AudioPlayHead::PositionInfo& positionInfo) noexcept; | |||
| }; | |||
| //============================================================================== | |||
| @@ -128,7 +128,7 @@ public: | |||
| bool processBlock (AudioBuffer<float>& buffer, | |||
| AudioProcessor::Realtime realtime, | |||
| const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override | |||
| const AudioPlayHead::PositionInfo& positionInfo) noexcept override | |||
| { | |||
| ignoreUnused (buffer, realtime, positionInfo); | |||
| return false; | |||
| @@ -189,7 +189,7 @@ public: | |||
| // isNonRealtime of the process context - typically preview is limited to realtime. | |||
| bool processBlock (AudioBuffer<float>& buffer, | |||
| AudioProcessor::Realtime isNonRealtime, | |||
| const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override | |||
| const AudioPlayHead::PositionInfo& positionInfo) noexcept override | |||
| { | |||
| ignoreUnused (buffer, isNonRealtime, positionInfo); | |||
| return true; | |||
| @@ -84,7 +84,7 @@ bool AudioProcessorARAExtension::releaseResourcesForARA() | |||
| bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer<float>& buffer, | |||
| AudioProcessor::Realtime realtime, | |||
| const AudioPlayHead::CurrentPositionInfo& positionInfo) | |||
| const AudioPlayHead::PositionInfo& positionInfo) | |||
| { | |||
| // validate that the host has prepared us before processing | |||
| ARA_VALIDATE_API_STATE (isPrepared); | |||
| @@ -109,12 +109,10 @@ bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer<float>& buffer, | |||
| juce::AudioProcessor::Realtime realtime, | |||
| AudioPlayHead* playhead) | |||
| { | |||
| AudioPlayHead::CurrentPositionInfo positionInfo; | |||
| if (! isBoundToARA() || ! playhead || ! playhead->getCurrentPosition (positionInfo)) | |||
| positionInfo.resetToDefault(); | |||
| return processBlockForARA (buffer, realtime, positionInfo); | |||
| return processBlockForARA (buffer, | |||
| realtime, | |||
| playhead != nullptr ? playhead->getPosition().orFallback (AudioPlayHead::PositionInfo{}) | |||
| : AudioPlayHead::PositionInfo{}); | |||
| } | |||
| //============================================================================== | |||
| @@ -144,7 +144,7 @@ protected: | |||
| */ | |||
| bool processBlockForARA (AudioBuffer<float>& buffer, | |||
| AudioProcessor::Realtime realtime, | |||
| const AudioPlayHead::CurrentPositionInfo& positionInfo); | |||
| const AudioPlayHead::PositionInfo& positionInfo); | |||
| /** Implementation helper for AudioProcessor::processBlock(). | |||
| @@ -122,6 +122,14 @@ struct ExtensionsVisitor | |||
| virtual void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)>) const = 0; | |||
| }; | |||
| ExtensionsVisitor() = default; | |||
| ExtensionsVisitor (const ExtensionsVisitor&) = default; | |||
| ExtensionsVisitor (ExtensionsVisitor&&) = default; | |||
| ExtensionsVisitor& operator= (const ExtensionsVisitor&) = default; | |||
| ExtensionsVisitor& operator= (ExtensionsVisitor&&) = default; | |||
| virtual ~ExtensionsVisitor() = default; | |||
| /** Will be called if there is no platform specific information available. */ | |||
| @@ -20,10 +20,6 @@ | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| #include <utility> | |||
| namespace juce | |||
| { | |||
| @@ -38,24 +34,40 @@ constexpr auto isNothrowSwappable = noexcept (swap (std::declval<T&>(), std::dec | |||
| } // namespace adlSwap | |||
| } // namespace detail | |||
| struct Nullopt {}; | |||
| /** A type representing the null state of an Optional. | |||
| Similar to std::nullopt_t. | |||
| */ | |||
| struct Nullopt | |||
| { | |||
| explicit constexpr Nullopt (int) {} | |||
| }; | |||
| constexpr Nullopt nullopt; | |||
| /** An object that can be used when constructing and comparing Optional instances. | |||
| Similar to std::nullopt. | |||
| */ | |||
| constexpr Nullopt nullopt { 0 }; | |||
| // Without this, our tests can emit "unreachable code" warnings during | |||
| // link time code generation. | |||
| JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4702) | |||
| /* For internal use only! | |||
| /** | |||
| A simple optional type. | |||
| Has similar (not necessarily identical!) semantics to std::optional. | |||
| This is intended to stand-in for std::optional while JUCE's minimum | |||
| supported language standard is lower than C++17. When the minimum language | |||
| standard moves to C++17, this class will probably be deprecated, in much | |||
| the same way that juce::ScopedPointer was deprecated in favour of | |||
| std::unique_ptr after C++11. | |||
| This isn't really intended to be used by JUCE clients. Instead, it's to be | |||
| used internally in JUCE code, with an API close-enough to std::optional | |||
| that the types can be swapped with fairly minor disruption at some point in | |||
| the future, but *without breaking any public APIs*. | |||
| @tags{Core} | |||
| */ | |||
| template <typename Value> | |||
| class Optional | |||
| @@ -97,26 +109,26 @@ class Optional | |||
| && NotConstructibleFromSimilarType<T, U>::value>; | |||
| public: | |||
| Optional() = default; | |||
| Optional() : placeholder() {} | |||
| Optional (Nullopt) noexcept {} | |||
| Optional (Nullopt) noexcept : placeholder() {} | |||
| template <typename U = Value, | |||
| typename = std::enable_if_t<std::is_constructible<Value, U&&>::value | |||
| && ! std::is_same<std::decay_t<U>, Optional>::value>> | |||
| Optional (U&& value) noexcept (noexcept (Value (std::forward<U> (value)))) | |||
| : valid (true) | |||
| : storage (std::forward<U> (value)), valid (true) | |||
| { | |||
| new (&storage) Value (std::forward<U> (value)); | |||
| } | |||
| Optional (Optional&& other) noexcept (noexcept (std::declval<Optional>().constructFrom (other))) | |||
| : placeholder() | |||
| { | |||
| constructFrom (other); | |||
| } | |||
| Optional (const Optional& other) | |||
| : valid (other.valid) | |||
| : placeholder(), valid (other.valid) | |||
| { | |||
| if (valid) | |||
| new (&storage) Value (*other); | |||
| @@ -124,13 +136,14 @@ public: | |||
| template <typename Other, typename = OptionalMoveConstructorEnabled<Value, Other>> | |||
| Optional (Optional<Other>&& other) noexcept (noexcept (std::declval<Optional>().constructFrom (other))) | |||
| : placeholder() | |||
| { | |||
| constructFrom (other); | |||
| } | |||
| template <typename Other, typename = OptionalCopyConstructorEnabled<Value, Other>> | |||
| Optional (const Optional<Other>& other) | |||
| : valid (other.hasValue()) | |||
| : placeholder(), valid (other.hasValue()) | |||
| { | |||
| if (valid) | |||
| new (&storage) Value (*other); | |||
| @@ -167,7 +180,7 @@ public: | |||
| return *this; | |||
| } | |||
| /* Maintains the strong exception safety guarantee. */ | |||
| /** Maintains the strong exception safety guarantee. */ | |||
| Optional& operator= (const Optional& other) | |||
| { | |||
| auto copy = other; | |||
| @@ -182,7 +195,7 @@ public: | |||
| return *this; | |||
| } | |||
| /* Maintains the strong exception safety guarantee. */ | |||
| /** Maintains the strong exception safety guarantee. */ | |||
| template <typename Other, typename = OptionalCopyAssignmentEnabled<Value, Other>> | |||
| Optional& operator= (const Optional<Other>& other) | |||
| { | |||
| @@ -211,7 +224,7 @@ public: | |||
| operator*().~Value(); | |||
| } | |||
| /* Like std::optional::value_or */ | |||
| /** Like std::optional::value_or */ | |||
| template <typename U> | |||
| Value orFallback (U&& fallback) const { return *this ? **this : std::forward<U> (fallback); } | |||
| @@ -271,12 +284,22 @@ private: | |||
| } | |||
| } | |||
| std::aligned_storage_t<sizeof (Value), alignof (Value)> storage; | |||
| union | |||
| { | |||
| char placeholder; | |||
| Value storage; | |||
| }; | |||
| bool valid = false; | |||
| }; | |||
| JUCE_END_IGNORE_WARNINGS_MSVC | |||
| template <typename Value> | |||
| Optional<std::decay_t<Value>> makeOptional (Value&& v) | |||
| { | |||
| return std::forward<Value> (v); | |||
| } | |||
| template <class T, class U> | |||
| bool operator== (const Optional<T>& lhs, const Optional<U>& rhs) | |||
| { | |||
| @@ -0,0 +1,476 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2020 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Some information about a document. | |||
| Each instance represents some information about the document at the point when the instance | |||
| was created. | |||
| Instance information is not updated automatically. If you think some file information may | |||
| have changed, create a new instance. | |||
| @tags{Core} | |||
| */ | |||
| class AndroidDocumentInfo | |||
| { | |||
| public: | |||
| AndroidDocumentInfo() = default; | |||
| /** True if this file really exists. */ | |||
| bool exists() const { return isJuceFlagSet (flagExists); } | |||
| /** True if this is a directory rather than a file. */ | |||
| bool isDirectory() const; | |||
| /** True if this is a file rather than a directory. */ | |||
| bool isFile() const { return type.isNotEmpty() && ! isDirectory(); } | |||
| /** True if this process has permission to read this file. | |||
| If this returns true, and the AndroidDocument refers to a file rather than a directory, | |||
| then AndroidDocument::createInputStream should work on this document. | |||
| */ | |||
| bool canRead() const { return isJuceFlagSet (flagHasReadPermission) && type.isNotEmpty(); } | |||
| /** True if this is a document that can be written, or a directory that can be modified. | |||
| If this returns true, and the AndroidDocument refers to a file rather than a directory, | |||
| then AndroidDocument::createOutputStream should work on this document. | |||
| */ | |||
| bool canWrite() const | |||
| { | |||
| return isJuceFlagSet (flagHasWritePermission) | |||
| && type.isNotEmpty() | |||
| && (isNativeFlagSet (flagSupportsWrite) | |||
| || isNativeFlagSet (flagSupportsDelete) | |||
| || isNativeFlagSet (flagDirSupportsCreate)); | |||
| } | |||
| /** True if this document can be removed completely from the filesystem. */ | |||
| bool canDelete() const { return isNativeFlagSet (flagSupportsDelete); } | |||
| /** True if this is a directory and adding child documents is supported. */ | |||
| bool canCreateChildren() const { return isNativeFlagSet (flagDirSupportsCreate); } | |||
| /** True if this document can be renamed. */ | |||
| bool canRename() const { return isNativeFlagSet (flagSupportsRename); } | |||
| /** True if this document can be copied. */ | |||
| bool canCopy() const { return isNativeFlagSet (flagSupportsCopy); } | |||
| /** True if this document can be moved. */ | |||
| bool canMove() const { return isNativeFlagSet (flagSupportsMove); } | |||
| /** True if this document isn't a physical file on storage. */ | |||
| bool isVirtual() const { return isNativeFlagSet (flagVirtualDocument); } | |||
| /** The user-facing name. | |||
| This may or may not contain a file extension. For files identified by a URL, the MIME type | |||
| is stored separately. | |||
| */ | |||
| String getName() const { return name; } | |||
| /** The MIME type of this document. */ | |||
| String getType() const { return isDirectory() ? String{} : type; } | |||
| /** Timestamp when a document was last modified, in milliseconds since January 1, 1970 00:00:00.0 UTC. | |||
| Use isLastModifiedValid() to determine whether or not the result of this | |||
| function is valid. | |||
| */ | |||
| int64 getLastModified() const { return isJuceFlagSet (flagValidModified) ? lastModified : 0; } | |||
| /** True if the filesystem provided a modification time. */ | |||
| bool isLastModifiedValid() const { return isJuceFlagSet (flagValidModified); } | |||
| /** The size of the document in bytes, if known. | |||
| Use isSizeInBytesValid() to determine whether or not the result of this | |||
| function is valid. | |||
| */ | |||
| int64 getSizeInBytes() const { return isJuceFlagSet (flagValidSize) ? sizeInBytes : 0; } | |||
| /** True if the filesystem provided a size in bytes. */ | |||
| bool isSizeInBytesValid() const { return isJuceFlagSet (flagValidSize); } | |||
| /** @internal */ | |||
| class Args; | |||
| private: | |||
| explicit AndroidDocumentInfo (Args); | |||
| bool isNativeFlagSet (int flag) const { return (nativeFlags & flag) != 0; } | |||
| bool isJuceFlagSet (int flag) const { return (juceFlags & flag) != 0; } | |||
| /* Native Android flags that might be set in the COLUMN_FLAGS for a particular document */ | |||
| enum | |||
| { | |||
| flagSupportsWrite = 0x0002, | |||
| flagSupportsDelete = 0x0004, | |||
| flagDirSupportsCreate = 0x0008, | |||
| flagSupportsRename = 0x0040, | |||
| flagSupportsCopy = 0x0080, | |||
| flagSupportsMove = 0x0100, | |||
| flagVirtualDocument = 0x0200, | |||
| }; | |||
| /* Flags for other binary properties that aren't exposed in COLUMN_FLAGS */ | |||
| enum | |||
| { | |||
| flagExists = 1 << 0, | |||
| flagValidModified = 1 << 1, | |||
| flagValidSize = 1 << 2, | |||
| flagHasReadPermission = 1 << 3, | |||
| flagHasWritePermission = 1 << 4, | |||
| }; | |||
| String name; | |||
| String type; | |||
| int64 lastModified = 0; | |||
| int64 sizeInBytes = 0; | |||
| int nativeFlags = 0, juceFlags = 0; | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| Represents a permission granted to an application to read and/or write to a particular document | |||
| or tree. | |||
| This class also contains static methods to request, revoke, and query the permissions of your | |||
| app. These functions are no-ops on all platforms other than Android. | |||
| @tags{Core} | |||
| */ | |||
| class AndroidDocumentPermission | |||
| { | |||
| public: | |||
| /** The url of the document with persisted permissions. */ | |||
| URL getUrl() const { return url; } | |||
| /** The time when the permissions were persisted, in milliseconds since January 1, 1970 00:00:00.0 UTC. */ | |||
| int64 getPersistedTime() const { return time; } | |||
| /** True if the permission allows read access. */ | |||
| bool isReadPermission() const { return read; } | |||
| /** True if the permission allows write access. */ | |||
| bool isWritePermission() const { return write; } | |||
| /** Gives your app access to a particular document or tree, even after the device is rebooted. | |||
| If you want to persist access to a folder selected through a native file chooser, make sure | |||
| to pass the exact URL returned by the file picker. Do NOT call AndroidDocument::fromTree | |||
| and then pass the result of getUrl to this function, as the resulting URL may differ from | |||
| the result of the file picker. | |||
| */ | |||
| static void takePersistentReadWriteAccess (const URL&); | |||
| /** Revokes persistent access to a document or tree. */ | |||
| static void releasePersistentReadWriteAccess (const URL&); | |||
| /** Returns all of the permissions that have previously been granted to the app, via | |||
| takePersistentReadWriteAccess(); | |||
| */ | |||
| static std::vector<AndroidDocumentPermission> getPersistedPermissions(); | |||
| private: | |||
| URL url; | |||
| int64 time = 0; | |||
| bool read = false, write = false; | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| Provides access to a document on Android devices. | |||
| In this context, a 'document' may be a file or a directory. | |||
| The main purpose of this class is to provide access to files in shared storage on Android. | |||
| On newer Android versions, such files cannot be accessed directly by a file path, and must | |||
| instead be read and modified using a new URI-based DocumentsContract API. | |||
| Example use-cases: | |||
| - After showing the system open dialog to allow the user to open a file, pass the FileChooser's | |||
| URL result to AndroidDocument::fromDocument. Then, you can use getInfo() to retrieve | |||
| information about the file, and createInputStream to read from the file. Other functions allow | |||
| moving, copying, and deleting the file. | |||
| - Similarly to the 'open' use-case, you may use createOutputStream to write to a file, normally | |||
| located using the system save dialog. | |||
| - To allow reading or writing to a tree of files in shared storage, you can show the system | |||
| open dialog in 'selects directories' mode, and pass the resulting URL to | |||
| AndroidDocument::fromTree. Then, you can iterate the files in the directory, query them, | |||
| and create new files. This is a good way to store multiple files that the user can access from | |||
| other apps, and that will be persistent after uninstalling and reinstalling your app. | |||
| Note that you probably do *not* need this class if your app only needs to access files in its | |||
| own internal sandbox. juce::File instances should work as expected in that case. | |||
| AndroidDocument is a bit like the DocumentFile class from the androidx extension library, | |||
| in that it represents a single document, and is implemented using DocumentsContract functions. | |||
| @tags{Core} | |||
| */ | |||
| class AndroidDocument | |||
| { | |||
| public: | |||
| /** Create a null document. */ | |||
| AndroidDocument(); | |||
| /** Create an AndroidDocument representing a file or directory at a particular path. | |||
| This is provided for use on older API versions (lower than 19), or on other platforms, so | |||
| that the same AndroidDocument API can be used regardless of the runtime platform version. | |||
| If the runtime platform version is 19 or higher, and you wish to work with a URI obtained | |||
| from a native file picker, use fromDocument() or fromTree() instead. | |||
| If this function fails, hasValue() will return false on the returned document. | |||
| */ | |||
| static AndroidDocument fromFile (const File& filePath); | |||
| /** Create an AndroidDocument representing a single document. | |||
| The argument should be a URL representing a document. Such URLs are returned by the system | |||
| file-picker when it is not in folder-selection mode. If you pass a tree URL, this function | |||
| will fail. | |||
| This function may fail on Android devices with API level 18 or lower, and on non-Android | |||
| platforms. If this function fails, hasValue() will return false on the returned document. | |||
| If calling this function fails, you may want to retry creating an AndroidDocument | |||
| with fromFile(), passing the result of URL::getLocalFile(). | |||
| */ | |||
| static AndroidDocument fromDocument (const URL& documentUrl); | |||
| /** Create an AndroidDocument representing the root of a tree of files. | |||
| The argument should be a URL representing a tree. Such URLs are returned by the system | |||
| file-picker when it is in folder-selection mode. If you pass a URL referring to a document | |||
| inside a tree, this will return a document referring to the root of the tree. If you pass | |||
| a URL referring to a single file, this will fail. | |||
| When targeting platform version 30 or later, access to the filesystem via file paths is | |||
| heavily restricted, and access to shared storage must use a new URI-based system instead. | |||
| At time of writing, apps uploaded to the Play Store must target API 30 or higher. | |||
| If you want read/write access to a shared folder, you must: | |||
| - Use a native FileChooser in canSelectDirectories mode, to allow the user to select a | |||
| folder that your app can access. Your app will only have access to the contents of this | |||
| directory; it cannot escape to the filesystem root. The system will not allow the user | |||
| to grant access to certain locations, including filesystem roots and the Download folder. | |||
| - Pass the URI that the user selected to fromTree(), and use the resulting AndroidDocument | |||
| to read/write to the file system. | |||
| This function may fail on Android devices with API level 20 or lower, and on non-Android | |||
| platforms. If this function fails, hasValue() will return false on the returned document. | |||
| */ | |||
| static AndroidDocument fromTree (const URL& treeUrl); | |||
| AndroidDocument (const AndroidDocument&); | |||
| AndroidDocument (AndroidDocument&&) noexcept; | |||
| AndroidDocument& operator= (const AndroidDocument&); | |||
| AndroidDocument& operator= (AndroidDocument&&) noexcept; | |||
| ~AndroidDocument(); | |||
| /** True if the URLs of the two documents match. */ | |||
| bool operator== (const AndroidDocument&) const; | |||
| /** False if the URLs of the two documents match. */ | |||
| bool operator!= (const AndroidDocument&) const; | |||
| /** Attempts to delete this document, and returns true on success. */ | |||
| bool deleteDocument() const; | |||
| /** Renames the document, and returns true on success. | |||
| This may cause the document's URI and metadata to change, so ensure to invalidate any | |||
| cached information about the document (URLs, AndroidDocumentInfo instances) after calling | |||
| this function. | |||
| */ | |||
| bool renameTo (const String& newDisplayName); | |||
| /** Attempts to create a new nested document with a particular type and name. | |||
| The type should be a standard MIME type string, e.g. "image/png", "text/plain". | |||
| The file name doesn't need to contain an extension, as this information is passed via the | |||
| type argument. If this document is File-based rather than URL-based, then an appropriate | |||
| file extension will be chosen based on the MIME type. | |||
| On failure, the returned AndroidDocument may be invalid, and will return false from hasValue(). | |||
| */ | |||
| AndroidDocument createChildDocumentWithTypeAndName (const String& type, const String& name) const; | |||
| /** Attempts to create a new nested directory with a particular name. | |||
| On failure, the returned AndroidDocument may be invalid, and will return false from hasValue(). | |||
| */ | |||
| AndroidDocument createChildDirectory (const String& name) const; | |||
| /** True if this object actually refers to a document. | |||
| If this function returns false, you *must not* call any function on this instance other | |||
| than the special member functions to copy, move, and/or destruct the instance. | |||
| */ | |||
| bool hasValue() const { return pimpl != nullptr; } | |||
| /** Like hasValue(), but allows declaring AndroidDocument instances directly in 'if' statements. */ | |||
| explicit operator bool() const { return hasValue(); } | |||
| /** Creates a stream for reading from this document. */ | |||
| std::unique_ptr<InputStream> createInputStream() const; | |||
| /** Creates a stream for writing to this document. */ | |||
| std::unique_ptr<OutputStream> createOutputStream() const; | |||
| /** Returns the content URL describing this document. */ | |||
| URL getUrl() const; | |||
| /** Fetches information about this document. */ | |||
| AndroidDocumentInfo getInfo() const; | |||
| /** Experimental: Attempts to copy this document to a new parent, and returns an AndroidDocument | |||
| representing the copy. | |||
| On failure, the returned AndroidDocument may be invalid, and will return false from hasValue(). | |||
| This function may fail if the document doesn't allow copying, and when using URI-based | |||
| documents on devices with API level 23 or lower. On failure, the returned AndroidDocument | |||
| will return false from hasValue(). In testing, copying was not supported on the Android | |||
| emulator for API 24, 30, or 31, so there's a good chance this function won't work on real | |||
| devices. | |||
| @see AndroidDocumentInfo::canCopy | |||
| */ | |||
| AndroidDocument copyDocumentToParentDocument (const AndroidDocument& target) const; | |||
| /** Experimental: Attempts to move this document from one parent to another, and returns true on | |||
| success. | |||
| This may cause the document's URI and metadata to change, so ensure to invalidate any | |||
| cached information about the document (URLs, AndroidDocumentInfo instances) after calling | |||
| this function. | |||
| This function may fail if the document doesn't allow moving, and when using URI-based | |||
| documents on devices with API level 23 or lower. | |||
| */ | |||
| bool moveDocumentFromParentToParent (const AndroidDocument& currentParent, | |||
| const AndroidDocument& newParent); | |||
| /** @internal */ | |||
| struct NativeInfo; | |||
| /** @internal */ | |||
| NativeInfo getNativeInfo() const; | |||
| private: | |||
| struct Utils; | |||
| class Pimpl; | |||
| explicit AndroidDocument (std::unique_ptr<Pimpl>); | |||
| void swap (AndroidDocument& other) noexcept { std::swap (other.pimpl, pimpl); } | |||
| std::unique_ptr<Pimpl> pimpl; | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| An iterator that visits child documents in a directory. | |||
| Instances of this iterator can be created by calling makeRecursive() or | |||
| makeNonRecursive(). The results of these functions can additionally be used | |||
| in standard algorithms, and in range-for loops: | |||
| @code | |||
| AndroidDocument findFileWithName (const AndroidDocument& parent, const String& name) | |||
| { | |||
| for (const auto& child : AndroidDocumentIterator::makeNonRecursive (parent)) | |||
| if (child.getInfo().getName() == name) | |||
| return child; | |||
| return AndroidDocument(); | |||
| } | |||
| std::vector<AndroidDocument> findAllChildrenRecursive (const AndroidDocument& parent) | |||
| { | |||
| std::vector<AndroidDocument> children; | |||
| std::copy (AndroidDocumentIterator::makeRecursive (doc), | |||
| AndroidDocumentIterator(), | |||
| std::back_inserter (children)); | |||
| return children; | |||
| } | |||
| @endcode | |||
| @tags{Core} | |||
| */ | |||
| class AndroidDocumentIterator final | |||
| { | |||
| public: | |||
| using difference_type = std::ptrdiff_t; | |||
| using pointer = void; | |||
| using iterator_category = std::input_iterator_tag; | |||
| /** Create an iterator that will visit each item in this directory. */ | |||
| static AndroidDocumentIterator makeNonRecursive (const AndroidDocument&); | |||
| /** Create an iterator that will visit each item in this directory, and all nested directories. */ | |||
| static AndroidDocumentIterator makeRecursive (const AndroidDocument&); | |||
| /** Creates an end/sentinel iterator. */ | |||
| AndroidDocumentIterator() = default; | |||
| bool operator== (const AndroidDocumentIterator& other) const noexcept { return pimpl == nullptr && other.pimpl == nullptr; } | |||
| bool operator!= (const AndroidDocumentIterator& other) const noexcept { return ! operator== (other); } | |||
| /** Returns the document to which this iterator points. */ | |||
| AndroidDocument operator*() const; | |||
| /** Moves this iterator to the next position. */ | |||
| AndroidDocumentIterator& operator++(); | |||
| /** Allows this iterator to be used directly in a range-for. */ | |||
| AndroidDocumentIterator begin() const { return *this; } | |||
| /** Allows this iterator to be used directly in a range-for. */ | |||
| AndroidDocumentIterator end() const { return AndroidDocumentIterator{}; } | |||
| private: | |||
| struct Utils; | |||
| struct Pimpl; | |||
| explicit AndroidDocumentIterator (std::unique_ptr<Pimpl>); | |||
| std::shared_ptr<Pimpl> pimpl; | |||
| }; | |||
| } // namespace juce | |||
| @@ -1146,11 +1146,18 @@ public: | |||
| expect (home.getChildFile ("./../xyz") == home.getParentDirectory().getChildFile ("xyz")); | |||
| expect (home.getChildFile ("a1/a2/a3/./../../a4") == home.getChildFile ("a1/a4")); | |||
| expect (! File().hasReadAccess()); | |||
| expect (! File().hasWriteAccess()); | |||
| expect (! tempFile.hasReadAccess()); | |||
| { | |||
| FileOutputStream fo (tempFile); | |||
| fo.write ("0123456789", 10); | |||
| } | |||
| expect (tempFile.hasReadAccess()); | |||
| expect (tempFile.exists()); | |||
| expect (tempFile.getSize() == 10); | |||
| expect (std::abs ((int) (tempFile.getLastModificationTime().toMilliseconds() - Time::getCurrentTime().toMilliseconds())) < 3000); | |||
| @@ -342,6 +342,12 @@ public: | |||
| */ | |||
| bool hasWriteAccess() const; | |||
| /** Checks whether a file can be read. | |||
| @returns true if it's possible to read this file. | |||
| */ | |||
| bool hasReadAccess() const; | |||
| /** Changes the write-permission of a file or directory. | |||
| @param shouldBeReadOnly whether to add or remove write-permission | |||
| @@ -33,17 +33,34 @@ struct MimeTypeTableEntry | |||
| static MimeTypeTableEntry table[641]; | |||
| }; | |||
| static StringArray getMimeTypesForFileExtension (const String& fileExtension) | |||
| static StringArray getMatches (const String& toMatch, | |||
| const char* MimeTypeTableEntry::* matchField, | |||
| const char* MimeTypeTableEntry::* returnField) | |||
| { | |||
| StringArray result; | |||
| for (auto type : MimeTypeTableEntry::table) | |||
| if (fileExtension == type.fileExtension) | |||
| result.add (type.mimeType); | |||
| if (toMatch == type.*matchField) | |||
| result.add (type.*returnField); | |||
| return result; | |||
| } | |||
| namespace MimeTypeTable | |||
| { | |||
| StringArray getMimeTypesForFileExtension (const String& fileExtension) | |||
| { | |||
| return getMatches (fileExtension, &MimeTypeTableEntry::fileExtension, &MimeTypeTableEntry::mimeType); | |||
| } | |||
| StringArray getFileExtensionsForMimeType (const String& mimeType) | |||
| { | |||
| return getMatches (mimeType, &MimeTypeTableEntry::mimeType, &MimeTypeTableEntry::fileExtension); | |||
| } | |||
| } // namespace MimeTypeTable | |||
| //============================================================================== | |||
| MimeTypeTableEntry MimeTypeTableEntry::table[641] = | |||
| { | |||
| @@ -2,15 +2,15 @@ | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| Copyright (c) 2020 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 7 End-User License | |||
| Agreement and JUCE Privacy Policy. | |||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||
| End User License Agreement: www.juce.com/juce-7-licence | |||
| End User License Agreement: www.juce.com/juce-6-licence | |||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| @@ -23,8 +23,20 @@ | |||
| ============================================================================== | |||
| */ | |||
| #pragma once | |||
| namespace juce | |||
| { | |||
| Image JUCE_API getIconFromApplication (const String&, int); | |||
| Image JUCE_API getIconFromApplication (const String&, int) { return {}; } | |||
| } | |||
| namespace MimeTypeTable | |||
| { | |||
| /* @internal */ | |||
| StringArray getMimeTypesForFileExtension (const String& fileExtension); | |||
| /* @internal */ | |||
| StringArray getFileExtensionsForMimeType (const String& mimeType); | |||
| } // namespace MimeTypeTable | |||
| } // namespace juce | |||
| @@ -127,13 +127,13 @@ | |||
| //============================================================================== | |||
| #include "containers/juce_AbstractFifo.cpp" | |||
| // #include "containers/juce_ArrayBase.cpp" | |||
| // #include "containers/juce_ListenerList.cpp" | |||
| #include "containers/juce_ArrayBase.cpp" | |||
| #include "containers/juce_ListenerList.cpp" | |||
| #include "containers/juce_NamedValueSet.cpp" | |||
| // #include "containers/juce_OwnedArray.cpp" | |||
| #include "containers/juce_OwnedArray.cpp" | |||
| #include "containers/juce_PropertySet.cpp" | |||
| // #include "containers/juce_ReferenceCountedArray.cpp" | |||
| // #include "containers/juce_SparseSet.cpp" | |||
| #include "containers/juce_ReferenceCountedArray.cpp" | |||
| #include "containers/juce_SparseSet.cpp" | |||
| #include "files/juce_DirectoryIterator.cpp" | |||
| #include "files/juce_RangedDirectoryIterator.cpp" | |||
| #include "files/juce_File.cpp" | |||
| @@ -144,17 +144,17 @@ | |||
| #include "logging/juce_FileLogger.cpp" | |||
| #include "logging/juce_Logger.cpp" | |||
| #include "maths/juce_BigInteger.cpp" | |||
| // #include "maths/juce_Expression.cpp" | |||
| #include "maths/juce_Expression.cpp" | |||
| #include "maths/juce_Random.cpp" | |||
| #include "memory/juce_MemoryBlock.cpp" | |||
| // #include "memory/juce_AllocationHooks.cpp" | |||
| // #include "misc/juce_RuntimePermissions.cpp" | |||
| #include "memory/juce_AllocationHooks.cpp" | |||
| #include "misc/juce_RuntimePermissions.cpp" | |||
| #include "misc/juce_Result.cpp" | |||
| // #include "misc/juce_Uuid.cpp" | |||
| // #include "misc/juce_ConsoleApplication.cpp" | |||
| #include "misc/juce_Uuid.cpp" | |||
| #include "misc/juce_ConsoleApplication.cpp" | |||
| #include "network/juce_MACAddress.cpp" | |||
| // #include "network/juce_NamedPipe.cpp" | |||
| // #include "network/juce_Socket.cpp" | |||
| #include "network/juce_NamedPipe.cpp" | |||
| #include "network/juce_Socket.cpp" | |||
| #include "network/juce_IPAddress.cpp" | |||
| #include "streams/juce_BufferedInputStream.cpp" | |||
| #include "streams/juce_FileInputSource.cpp" | |||
| @@ -171,27 +171,27 @@ | |||
| #include "text/juce_StringArray.cpp" | |||
| #include "text/juce_StringPairArray.cpp" | |||
| #include "text/juce_StringPool.cpp" | |||
| // #include "text/juce_TextDiff.cpp" | |||
| #include "text/juce_TextDiff.cpp" | |||
| #include "text/juce_Base64.cpp" | |||
| #include "threads/juce_ReadWriteLock.cpp" | |||
| #include "threads/juce_Thread.cpp" | |||
| #include "threads/juce_ThreadPool.cpp" | |||
| // #include "threads/juce_TimeSliceThread.cpp" | |||
| // #include "time/juce_PerformanceCounter.cpp" | |||
| #include "threads/juce_TimeSliceThread.cpp" | |||
| #include "time/juce_PerformanceCounter.cpp" | |||
| #include "time/juce_RelativeTime.cpp" | |||
| #include "time/juce_Time.cpp" | |||
| // #include "unit_tests/juce_UnitTest.cpp" | |||
| #include "unit_tests/juce_UnitTest.cpp" | |||
| #include "containers/juce_Variant.cpp" | |||
| #include "javascript/juce_JSON.cpp" | |||
| // #include "javascript/juce_Javascript.cpp" | |||
| #include "javascript/juce_Javascript.cpp" | |||
| #include "containers/juce_DynamicObject.cpp" | |||
| #include "xml/juce_XmlDocument.cpp" | |||
| #include "xml/juce_XmlElement.cpp" | |||
| #include "zip/juce_GZIPDecompressorInputStream.cpp" | |||
| #include "zip/juce_GZIPCompressorOutputStream.cpp" | |||
| // #include "zip/juce_ZipFile.cpp" | |||
| // #include "files/juce_FileFilter.cpp" | |||
| // #include "files/juce_WildcardFileFilter.cpp" | |||
| #include "zip/juce_ZipFile.cpp" | |||
| #include "files/juce_FileFilter.cpp" | |||
| #include "files/juce_WildcardFileFilter.cpp" | |||
| //============================================================================== | |||
| #if ! JUCE_WINDOWS | |||
| @@ -236,6 +236,7 @@ | |||
| //============================================================================== | |||
| #elif JUCE_ANDROID | |||
| #include "native/juce_linux_CommonFile.cpp" | |||
| #include "native/juce_android_AndroidDocument.cpp" | |||
| #include "native/juce_android_JNIHelpers.cpp" | |||
| #include "native/juce_android_Files.cpp" | |||
| #include "native/juce_android_Misc.cpp" | |||
| @@ -249,6 +250,8 @@ | |||
| #endif | |||
| #include "files/juce_common_MimeTypes.h" | |||
| #include "files/juce_common_MimeTypes.cpp" | |||
| #include "threads/juce_HighResolutionTimer.cpp" | |||
| #include "threads/juce_WaitableEvent.cpp" | |||
| #include "network/juce_URL.cpp" | |||
| @@ -263,7 +266,6 @@ | |||
| #if JUCE_UNIT_TESTS | |||
| #include "containers/juce_HashMap_test.cpp" | |||
| #include "containers/juce_Optional.h" | |||
| #include "containers/juce_Optional_test.cpp" | |||
| #endif | |||
| @@ -32,7 +32,7 @@ | |||
| ID: juce_core | |||
| vendor: juce | |||
| version: 6.1.6 | |||
| version: 7.0.1 | |||
| name: JUCE core classes | |||
| description: The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality. | |||
| website: http://www.juce.com/juce | |||
| @@ -244,6 +244,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC | |||
| #include "memory/juce_ReferenceCountedObject.h" | |||
| #include "memory/juce_ScopedPointer.h" | |||
| #include "memory/juce_OptionalScopedPointer.h" | |||
| #include "containers/juce_Optional.h" | |||
| #include "containers/juce_ScopedValueSetter.h" | |||
| #include "memory/juce_Singleton.h" | |||
| #include "memory/juce_WeakReference.h" | |||
| @@ -342,6 +343,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC | |||
| #include "memory/juce_SharedResourcePointer.h" | |||
| #include "memory/juce_AllocationHooks.h" | |||
| #include "memory/juce_Reservoir.h" | |||
| #include "files/juce_AndroidDocument.h" | |||
| #if JUCE_CORE_INCLUDE_OBJC_HELPERS && (JUCE_MAC || JUCE_IOS) | |||
| #include "native/juce_mac_ObjCHelpers.h" | |||
| @@ -154,6 +154,8 @@ | |||
| #include <winsock2.h> | |||
| #include <ws2tcpip.h> | |||
| #include <iphlpapi.h> | |||
| #include <accctrl.h> | |||
| #include <aclapi.h> | |||
| #if ! JUCE_CXX17_IS_AVAILABLE | |||
| #pragma push_macro ("WIN_NOEXCEPT") | |||
| @@ -1,691 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (constructor, "<init>", "(Landroid/content/Context;Landroid/media/MediaScannerConnection$MediaScannerConnectionClient;)V") \ | |||
| METHOD (connect, "connect", "()V") \ | |||
| METHOD (disconnect, "disconnect", "()V") \ | |||
| METHOD (scanFile, "scanFile", "(Ljava/lang/String;Ljava/lang/String;)V") \ | |||
| DECLARE_JNI_CLASS (MediaScannerConnection, "android/media/MediaScannerConnection") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (query, "query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;") \ | |||
| METHOD (openInputStream, "openInputStream", "(Landroid/net/Uri;)Ljava/io/InputStream;") \ | |||
| METHOD (openOutputStream, "openOutputStream", "(Landroid/net/Uri;)Ljava/io/OutputStream;") | |||
| DECLARE_JNI_CLASS (ContentResolver, "android/content/ContentResolver") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (moveToFirst, "moveToFirst", "()Z") \ | |||
| METHOD (getColumnIndex, "getColumnIndex", "(Ljava/lang/String;)I") \ | |||
| METHOD (getString, "getString", "(I)Ljava/lang/String;") \ | |||
| METHOD (close, "close", "()V") \ | |||
| DECLARE_JNI_CLASS (AndroidCursor, "android/database/Cursor") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (getExternalStorageDirectory, "getExternalStorageDirectory", "()Ljava/io/File;") \ | |||
| STATICMETHOD (getExternalStoragePublicDirectory, "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;") \ | |||
| STATICMETHOD (getDataDirectory, "getDataDirectory", "()Ljava/io/File;") | |||
| DECLARE_JNI_CLASS (AndroidEnvironment, "android/os/Environment") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (close, "close", "()V") \ | |||
| METHOD (flush, "flush", "()V") \ | |||
| METHOD (write, "write", "([BII)V") | |||
| DECLARE_JNI_CLASS (AndroidOutputStream, "java/io/OutputStream") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| FIELD (publicSourceDir, "publicSourceDir", "Ljava/lang/String;") \ | |||
| FIELD (dataDir, "dataDir", "Ljava/lang/String;") | |||
| DECLARE_JNI_CLASS (AndroidApplicationInfo, "android/content/pm/ApplicationInfo") | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| static File juceFile (LocalRef<jobject> obj) | |||
| { | |||
| auto* env = getEnv(); | |||
| if (env->IsInstanceOf (obj.get(), JavaFile) != 0) | |||
| return File (juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (obj.get(), | |||
| JavaFile.getAbsolutePath)))); | |||
| return {}; | |||
| } | |||
| static File getWellKnownFolder (const char* folderId) | |||
| { | |||
| auto* env = getEnv(); | |||
| auto fieldId = env->GetStaticFieldID (AndroidEnvironment, folderId, "Ljava/lang/String;"); | |||
| if (fieldId == nullptr) | |||
| { | |||
| // unknown field in environment | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| LocalRef<jobject> fieldValue (env->GetStaticObjectField (AndroidEnvironment, fieldId)); | |||
| if (fieldValue == nullptr) | |||
| return {}; | |||
| LocalRef<jobject> downloadFolder (env->CallStaticObjectMethod (AndroidEnvironment, | |||
| AndroidEnvironment.getExternalStoragePublicDirectory, | |||
| fieldValue.get())); | |||
| return (downloadFolder ? juceFile (downloadFolder) : File()); | |||
| } | |||
| static LocalRef<jobject> urlToUri (const URL& url) | |||
| { | |||
| return LocalRef<jobject> (getEnv()->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, | |||
| javaString (url.toString (true)).get())); | |||
| } | |||
| //============================================================================== | |||
| struct AndroidContentUriResolver | |||
| { | |||
| public: | |||
| static LocalRef<jobject> getStreamForContentUri (const URL& url, bool inputStream) | |||
| { | |||
| // only use this method for content URIs | |||
| jassert (url.getScheme() == "content"); | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver)); | |||
| if (contentResolver) | |||
| return LocalRef<jobject> ((env->CallObjectMethod (contentResolver.get(), | |||
| inputStream ? ContentResolver.openInputStream | |||
| : ContentResolver.openOutputStream, | |||
| urlToUri (url).get()))); | |||
| return LocalRef<jobject>(); | |||
| } | |||
| static File getLocalFileFromContentUri (const URL& url) | |||
| { | |||
| // only use this method for content URIs | |||
| jassert (url.getScheme() == "content"); | |||
| auto authority = url.getDomain(); | |||
| auto documentId = URL::removeEscapeChars (url.getSubPath().fromFirstOccurrenceOf ("/", false, false)); | |||
| auto tokens = StringArray::fromTokens (documentId, ":", ""); | |||
| if (authority == "com.android.externalstorage.documents") | |||
| { | |||
| auto storageId = tokens[0]; | |||
| auto subpath = tokens[1]; | |||
| auto storagePath = getStorageDevicePath (storageId); | |||
| if (storagePath != File()) | |||
| return storagePath.getChildFile (subpath); | |||
| } | |||
| else if (authority == "com.android.providers.downloads.documents") | |||
| { | |||
| auto type = tokens[0]; | |||
| auto downloadId = tokens[1]; | |||
| if (type.equalsIgnoreCase ("raw")) | |||
| { | |||
| return File (downloadId); | |||
| } | |||
| else if (type.equalsIgnoreCase ("downloads")) | |||
| { | |||
| auto subDownloadPath = url.getSubPath().fromFirstOccurrenceOf ("tree/downloads", false, false); | |||
| return File (getWellKnownFolder ("DIRECTORY_DOWNLOADS").getFullPathName() + "/" + subDownloadPath); | |||
| } | |||
| else | |||
| { | |||
| return getLocalFileFromContentUri (URL ("content://downloads/public_downloads/" + documentId)); | |||
| } | |||
| } | |||
| else if (authority == "com.android.providers.media.documents" && documentId.isNotEmpty()) | |||
| { | |||
| auto type = tokens[0]; | |||
| auto mediaId = tokens[1]; | |||
| if (type == "image") | |||
| type = "images"; | |||
| return getCursorDataColumn (URL ("content://media/external/" + type + "/media"), | |||
| "_id=?", StringArray {mediaId}); | |||
| } | |||
| return getCursorDataColumn (url); | |||
| } | |||
| static String getFileNameFromContentUri (const URL& url) | |||
| { | |||
| auto uri = urlToUri (url); | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver)); | |||
| if (contentResolver == nullptr) | |||
| return {}; | |||
| auto filename = getStringUsingDataColumn ("_display_name", env, uri, contentResolver); | |||
| // Fallback to "_data" column | |||
| if (filename.isEmpty()) | |||
| { | |||
| auto path = getStringUsingDataColumn ("_data", env, uri, contentResolver); | |||
| filename = path.fromLastOccurrenceOf ("/", false, true); | |||
| } | |||
| return filename; | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| static String getCursorDataColumn (const URL& url, const String& selection = {}, | |||
| const StringArray& selectionArgs = {}) | |||
| { | |||
| auto uri = urlToUri (url); | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver)); | |||
| if (contentResolver) | |||
| { | |||
| LocalRef<jstring> columnName (javaString ("_data")); | |||
| LocalRef<jobjectArray> projection (env->NewObjectArray (1, JavaString, columnName.get())); | |||
| LocalRef<jobjectArray> args; | |||
| if (selection.isNotEmpty()) | |||
| { | |||
| args = LocalRef<jobjectArray> (env->NewObjectArray (selectionArgs.size(), JavaString, javaString ("").get())); | |||
| for (int i = 0; i < selectionArgs.size(); ++i) | |||
| env->SetObjectArrayElement (args.get(), i, javaString (selectionArgs[i]).get()); | |||
| } | |||
| LocalRef<jstring> jSelection (selection.isNotEmpty() ? javaString (selection) : LocalRef<jstring>()); | |||
| LocalRef<jobject> cursor (env->CallObjectMethod (contentResolver.get(), ContentResolver.query, | |||
| uri.get(), projection.get(), jSelection.get(), | |||
| args.get(), nullptr)); | |||
| if (jniCheckHasExceptionOccurredAndClear()) | |||
| { | |||
| // An exception has occurred, have you acquired RuntimePermission::readExternalStorage permission? | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| if (cursor) | |||
| { | |||
| if (env->CallBooleanMethod (cursor.get(), AndroidCursor.moveToFirst) != 0) | |||
| { | |||
| auto columnIndex = env->CallIntMethod (cursor.get(), AndroidCursor.getColumnIndex, columnName.get()); | |||
| if (columnIndex >= 0) | |||
| { | |||
| LocalRef<jstring> value ((jstring) env->CallObjectMethod (cursor.get(), AndroidCursor.getString, columnIndex)); | |||
| if (value) | |||
| return juceString (value.get()); | |||
| } | |||
| } | |||
| env->CallVoidMethod (cursor.get(), AndroidCursor.close); | |||
| } | |||
| } | |||
| return {}; | |||
| } | |||
| //============================================================================== | |||
| static File getStorageDevicePath (const String& storageId) | |||
| { | |||
| // check for the primary alias | |||
| if (storageId == "primary") | |||
| return getPrimaryStorageDirectory(); | |||
| auto storageDevices = getSecondaryStorageDirectories(); | |||
| for (auto storageDevice : storageDevices) | |||
| if (getStorageIdForMountPoint (storageDevice) == storageId) | |||
| return storageDevice; | |||
| return {}; | |||
| } | |||
| static File getPrimaryStorageDirectory() | |||
| { | |||
| auto* env = getEnv(); | |||
| return juceFile (LocalRef<jobject> (env->CallStaticObjectMethod (AndroidEnvironment, AndroidEnvironment.getExternalStorageDirectory))); | |||
| } | |||
| static Array<File> getSecondaryStorageDirectories() | |||
| { | |||
| Array<File> results; | |||
| if (getAndroidSDKVersion() >= 19) | |||
| { | |||
| auto* env = getEnv(); | |||
| static jmethodID m = (env->GetMethodID (AndroidContext, "getExternalFilesDirs", | |||
| "(Ljava/lang/String;)[Ljava/io/File;")); | |||
| if (m == nullptr) | |||
| return {}; | |||
| auto paths = convertFileArray (LocalRef<jobject> (env->CallObjectMethod (getAppContext().get(), m, nullptr))); | |||
| for (auto path : paths) | |||
| results.add (getMountPointForFile (path)); | |||
| } | |||
| else | |||
| { | |||
| // on older SDKs other external storages are located "next" to the primary | |||
| // storage mount point | |||
| auto mountFolder = getMountPointForFile (getPrimaryStorageDirectory()) | |||
| .getParentDirectory(); | |||
| // don't include every folder. Only folders which are actually mountpoints | |||
| juce_statStruct info; | |||
| if (! juce_stat (mountFolder.getFullPathName(), info)) | |||
| return {}; | |||
| auto rootFsDevice = info.st_dev; | |||
| for (const auto& iter : RangedDirectoryIterator (mountFolder, false, "*", File::findDirectories)) | |||
| { | |||
| auto candidate = iter.getFile(); | |||
| if (juce_stat (candidate.getFullPathName(), info) | |||
| && info.st_dev != rootFsDevice) | |||
| results.add (candidate); | |||
| } | |||
| } | |||
| return results; | |||
| } | |||
| //============================================================================== | |||
| static String getStorageIdForMountPoint (const File& mountpoint) | |||
| { | |||
| // currently this seems to work fine, but something | |||
| // more intelligent may be needed in the future | |||
| return mountpoint.getFileName(); | |||
| } | |||
| static File getMountPointForFile (const File& file) | |||
| { | |||
| juce_statStruct info; | |||
| if (juce_stat (file.getFullPathName(), info)) | |||
| { | |||
| auto dev = info.st_dev; | |||
| File mountPoint = file; | |||
| for (;;) | |||
| { | |||
| auto parent = mountPoint.getParentDirectory(); | |||
| if (parent == mountPoint) | |||
| break; | |||
| juce_stat (parent.getFullPathName(), info); | |||
| if (info.st_dev != dev) | |||
| break; | |||
| mountPoint = parent; | |||
| } | |||
| return mountPoint; | |||
| } | |||
| return {}; | |||
| } | |||
| //============================================================================== | |||
| static Array<File> convertFileArray (LocalRef<jobject> obj) | |||
| { | |||
| auto* env = getEnv(); | |||
| int n = (int) env->GetArrayLength ((jobjectArray) obj.get()); | |||
| Array<File> files; | |||
| for (int i = 0; i < n; ++i) | |||
| files.add (juceFile (LocalRef<jobject> (env->GetObjectArrayElement ((jobjectArray) obj.get(), | |||
| (jsize) i)))); | |||
| return files; | |||
| } | |||
| //============================================================================== | |||
| static String getStringUsingDataColumn (const String& columnNameToUse, JNIEnv* env, | |||
| const LocalRef<jobject>& uri, | |||
| const LocalRef<jobject>& contentResolver) | |||
| { | |||
| LocalRef<jstring> columnName (javaString (columnNameToUse)); | |||
| LocalRef<jobjectArray> projection (env->NewObjectArray (1, JavaString, columnName.get())); | |||
| LocalRef<jobject> cursor (env->CallObjectMethod (contentResolver.get(), ContentResolver.query, | |||
| uri.get(), projection.get(), nullptr, | |||
| nullptr, nullptr)); | |||
| if (jniCheckHasExceptionOccurredAndClear()) | |||
| { | |||
| // An exception has occurred, have you acquired RuntimePermission::readExternalStorage permission? | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| if (cursor == nullptr) | |||
| return {}; | |||
| String fileName; | |||
| if (env->CallBooleanMethod (cursor.get(), AndroidCursor.moveToFirst) != 0) | |||
| { | |||
| auto columnIndex = env->CallIntMethod (cursor.get(), AndroidCursor.getColumnIndex, columnName.get()); | |||
| if (columnIndex >= 0) | |||
| { | |||
| LocalRef<jstring> value ((jstring) env->CallObjectMethod (cursor.get(), AndroidCursor.getString, columnIndex)); | |||
| if (value) | |||
| fileName = juceString (value.get()); | |||
| } | |||
| } | |||
| env->CallVoidMethod (cursor.get(), AndroidCursor.close); | |||
| return fileName; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| struct AndroidContentUriOutputStream : public OutputStream | |||
| { | |||
| AndroidContentUriOutputStream (LocalRef<jobject>&& outputStream) | |||
| : stream (outputStream) | |||
| { | |||
| } | |||
| ~AndroidContentUriOutputStream() override | |||
| { | |||
| stream.callVoidMethod (AndroidOutputStream.close); | |||
| } | |||
| void flush() override | |||
| { | |||
| stream.callVoidMethod (AndroidOutputStream.flush); | |||
| } | |||
| bool setPosition (int64 newPos) override | |||
| { | |||
| return (newPos == pos); | |||
| } | |||
| int64 getPosition() override | |||
| { | |||
| return pos; | |||
| } | |||
| bool write (const void* dataToWrite, size_t numberOfBytes) override | |||
| { | |||
| if (numberOfBytes == 0) | |||
| return true; | |||
| JNIEnv* env = getEnv(); | |||
| jbyteArray javaArray = env->NewByteArray ((jsize) numberOfBytes); | |||
| env->SetByteArrayRegion (javaArray, 0, (jsize) numberOfBytes, (const jbyte*) dataToWrite); | |||
| stream.callVoidMethod (AndroidOutputStream.write, javaArray, 0, (jint) numberOfBytes); | |||
| env->DeleteLocalRef (javaArray); | |||
| pos += static_cast<int64> (numberOfBytes); | |||
| return true; | |||
| } | |||
| GlobalRef stream; | |||
| int64 pos = 0; | |||
| }; | |||
| static OutputStream* juce_CreateContentURIOutputStream (const URL& url) | |||
| { | |||
| auto stream = AndroidContentUriResolver::getStreamForContentUri (url, false); | |||
| return (stream.get() != nullptr ? new AndroidContentUriOutputStream (std::move (stream)) : nullptr); | |||
| } | |||
| //============================================================================== | |||
| class MediaScannerConnectionClient : public AndroidInterfaceImplementer | |||
| { | |||
| public: | |||
| virtual void onMediaScannerConnected() = 0; | |||
| virtual void onScanCompleted() = 0; | |||
| private: | |||
| jobject invoke (jobject proxy, jobject method, jobjectArray args) override | |||
| { | |||
| auto* env = getEnv(); | |||
| auto methodName = juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName)); | |||
| if (methodName == "onMediaScannerConnected") | |||
| { | |||
| onMediaScannerConnected(); | |||
| return nullptr; | |||
| } | |||
| else if (methodName == "onScanCompleted") | |||
| { | |||
| onScanCompleted(); | |||
| return nullptr; | |||
| } | |||
| return AndroidInterfaceImplementer::invoke (proxy, method, args); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| bool File::isOnCDRomDrive() const | |||
| { | |||
| return false; | |||
| } | |||
| bool File::isOnHardDisk() const | |||
| { | |||
| return true; | |||
| } | |||
| bool File::isOnRemovableDrive() const | |||
| { | |||
| return false; | |||
| } | |||
| String File::getVersion() const | |||
| { | |||
| return {}; | |||
| } | |||
| static File getDocumentsDirectory() | |||
| { | |||
| auto* env = getEnv(); | |||
| if (getAndroidSDKVersion() >= 19) | |||
| return getWellKnownFolder ("DIRECTORY_DOCUMENTS"); | |||
| return juceFile (LocalRef<jobject> (env->CallStaticObjectMethod (AndroidEnvironment, AndroidEnvironment.getDataDirectory))); | |||
| } | |||
| static File getAppDataDir (bool dataDir) | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> applicationInfo (env->CallObjectMethod (getAppContext().get(), AndroidContext.getApplicationInfo)); | |||
| LocalRef<jobject> jString (env->GetObjectField (applicationInfo.get(), dataDir ? AndroidApplicationInfo.dataDir : AndroidApplicationInfo.publicSourceDir)); | |||
| return {juceString ((jstring) jString.get())}; | |||
| } | |||
| File File::getSpecialLocation (const SpecialLocationType type) | |||
| { | |||
| switch (type) | |||
| { | |||
| case userHomeDirectory: | |||
| case userApplicationDataDirectory: | |||
| case userDesktopDirectory: | |||
| case commonApplicationDataDirectory: | |||
| { | |||
| static File appDataDir = getAppDataDir (true); | |||
| return appDataDir; | |||
| } | |||
| case userDocumentsDirectory: | |||
| case commonDocumentsDirectory: | |||
| { | |||
| static auto docsDir = getDocumentsDirectory(); | |||
| return docsDir; | |||
| } | |||
| case userPicturesDirectory: | |||
| { | |||
| static auto picturesDir = getWellKnownFolder ("DIRECTORY_PICTURES"); | |||
| return picturesDir; | |||
| } | |||
| case userMusicDirectory: | |||
| { | |||
| static auto musicDir = getWellKnownFolder ("DIRECTORY_MUSIC"); | |||
| return musicDir; | |||
| } | |||
| case userMoviesDirectory: | |||
| { | |||
| static auto moviesDir = getWellKnownFolder ("DIRECTORY_MOVIES"); | |||
| return moviesDir; | |||
| } | |||
| case globalApplicationsDirectory: | |||
| return File ("/system/app"); | |||
| case tempDirectory: | |||
| { | |||
| File tmp = getSpecialLocation (commonApplicationDataDirectory).getChildFile (".temp"); | |||
| tmp.createDirectory(); | |||
| return File (tmp.getFullPathName()); | |||
| } | |||
| case invokedExecutableFile: | |||
| case currentExecutableFile: | |||
| case currentApplicationFile: | |||
| case hostApplicationPath: | |||
| return getAppDataDir (false); | |||
| default: | |||
| jassertfalse; // unknown type? | |||
| break; | |||
| } | |||
| return {}; | |||
| } | |||
| bool File::moveToTrash() const | |||
| { | |||
| if (! exists()) | |||
| return true; | |||
| // TODO | |||
| return false; | |||
| } | |||
| JUCE_API bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String&) | |||
| { | |||
| URL targetURL (fileName); | |||
| auto* env = getEnv(); | |||
| const LocalRef<jstring> action (javaString ("android.intent.action.VIEW")); | |||
| LocalRef<jobject> intent (env->NewObject (AndroidIntent, AndroidIntent.constructWithUri, action.get(), urlToUri (targetURL).get())); | |||
| env->CallVoidMethod (getCurrentActivity(), AndroidContext.startActivity, intent.get()); | |||
| return true; | |||
| } | |||
| void File::revealToUser() const | |||
| { | |||
| } | |||
| //============================================================================== | |||
| class SingleMediaScanner : public MediaScannerConnectionClient | |||
| { | |||
| public: | |||
| SingleMediaScanner (const String& filename) | |||
| : msc (LocalRef<jobject> (getEnv()->NewObject (MediaScannerConnection, | |||
| MediaScannerConnection.constructor, | |||
| getAppContext().get(), | |||
| CreateJavaInterface (this, "android/media/MediaScannerConnection$MediaScannerConnectionClient").get()))), | |||
| file (filename) | |||
| { | |||
| getEnv()->CallVoidMethod (msc.get(), MediaScannerConnection.connect); | |||
| } | |||
| void onMediaScannerConnected() override | |||
| { | |||
| auto* env = getEnv(); | |||
| env->CallVoidMethod (msc.get(), MediaScannerConnection.scanFile, javaString (file).get(), 0); | |||
| } | |||
| void onScanCompleted() override | |||
| { | |||
| getEnv()->CallVoidMethod (msc.get(), MediaScannerConnection.disconnect); | |||
| } | |||
| private: | |||
| GlobalRef msc; | |||
| String file; | |||
| }; | |||
| void FileOutputStream::flushInternal() | |||
| { | |||
| if (fileHandle != nullptr) | |||
| { | |||
| if (fsync (getFD (fileHandle)) == -1) | |||
| status = getResultForErrno(); | |||
| // This stuff tells the OS to asynchronously update the metadata | |||
| // that the OS has cached about the file - this metadata is used | |||
| // when the device is acting as a USB drive, and unless it's explicitly | |||
| // refreshed, it'll get out of step with the real file. | |||
| new SingleMediaScanner (file.getFullPathName()); | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -1,701 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| static const uint8 invocationHandleByteCode[] = | |||
| {31,139,8,8,215,115,161,94,0,3,105,110,118,111,99,97,116,105,111,110,72,97,110,100,108,101,66,121,116,101,67,111,100,101,46, | |||
| 100,101,120,0,109,148,65,107,19,65,20,199,223,236,78,146,90,211,116,77,141,214,88,33,151,130,7,117,91,172,80,73,17,161,32,53,93, | |||
| 17,108,233,65,5,217,38,155,102,219,237,110,220,108,99,172,8,173,40,42,244,36,245,226,65,232,165,138,7,15,226,65,193,147,120,244, | |||
| 166,130,95,192,155,23,189,21,68,252,207,206,180,137,218,133,223,204,155,247,222,206,123,111,119,230,85,156,86,247,208,201, | |||
| 83,84,121,127,155,63,122,245,209,24,205,141,63,156,124,186,176,229,110,12,151,158,157,126,219,76,39,136,234,68,212,154,25,201, | |||
| 146,122,38,56,81,158,164,126,15,248,10,160,162,95,128,129,99,24,82,152,71,152,92,123,24,86,116,162,53,204,203,26,209,29,112, | |||
| 15,108,128,231,224,37,248,2,126,128,4,252,6,192,56,184,6,102,65,21,220,2,171,224,1,120,2,94,128,215,224,29,248,0,62,129,111,224, | |||
| 59,248,169,203,184,72,157,146,36,115,233,82,185,118,131,189,160,7,232,138,171,154,204,95,200,53,77,218,83,170,214,180,146, | |||
| 35,77,238,153,139,107,212,99,27,35,141,122,213,218,80,181,239,83,250,108,60,51,234,139,247,213,148,191,68,188,61,13,149,208,142, | |||
| 97,24,228,180,99,63,194,69,206,122,44,111,233,50,223,186,33,52,7,32,93,30,2,35,68,25,229,1,95,46,235,140,173,5,97,17,107,153, | |||
| 97,154,203,245,212,89,216,17,103,24,33,71,81,141,88,215,135,52,226,44,131,90,121,252,141,250,184,172,109,170,222,233,219,67,83, | |||
| 33,234,191,158,186,155,122,156,218,108,38,69,212,52,106,204,210,209,223,180,243,48,53,139,60,214,88,251,219,203,178,116,236, | |||
| 223,165,190,117,50,254,15,42,243,49,215,119,163,51,196,74,148,47,45,149,157,243,126,51,40,219,145,27,248,19,182,95,241,156,240, | |||
| 196,188,221,180,41,97,149,44,203,34,110,137,113,208,42,7,139,102,184,216,240,204,121,188,98,238,250,94,145,242,86,197,246,154, | |||
| 238,130,105,251,126,16,197,54,115,186,22,6,55,26,69,202,90,98,91,211,179,253,57,243,226,236,188,83,142,138,148,235,208,197,126, | |||
| 246,172,231,20,17,173,173,14,157,170,7,95,115,215,104,255,187,93,112,162,90,80,41,18,155,33,109,166,68,125,87,118,137,202,237, | |||
| 112,174,65,137,178,231,216,33,25,21,183,81,183,163,114,237,156,235,219,158,187,236,80,102,91,35,66,46,56,212,85,221,182,36,93, | |||
| 169,73,46,198,81,168,199,71,66,77,103,60,240,35,167,21,145,241,215,242,146,83,165,68,61,12,90,55,137,71,53,23,1,155,182,183, | |||
| 132,237,216,193,84,70,203,23,181,185,210,113,156,146,84,102,146,246,99,188,127,153,14,235,253,185,94,72,155,164,105,236,208,0, | |||
| 235,231,196,116,113,134,87,87,248,186,174,225,246,50,1,123,163,235,236,179,206,216,138,248,207,198,63,103,65,204,219,61,66,235, | |||
| 232,19,122,71,175,224,29,253,34,65,237,158,145,164,118,223,208,13,41,199,231,170,32,223,89,23,62,5,169,23,247,135,25,82,31,223, | |||
| 169,130,140,43,250,140,174,252,197,61,226,133,246,253,34,37,15,170,196,133,44,122,218,31,165,24,139,249,12,5,0,0,0,0}; | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (newProxyInstance, "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;") \ | |||
| DECLARE_JNI_CLASS (JavaProxy, "java/lang/reflect/Proxy") | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (constructor, "<init>", "(J)V") \ | |||
| METHOD (clear, "clear", "()V") \ | |||
| CALLBACK (juce_invokeImplementer, "dispatchInvoke", "(JLjava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;") \ | |||
| CALLBACK (juce_dispatchDelete, "dispatchFinalize", "(J)V") | |||
| DECLARE_JNI_CLASS_WITH_BYTECODE (JuceInvocationHandler, "com/rmsl/juce/JuceInvocationHandler", 10, invocationHandleByteCode, sizeof (invocationHandleByteCode)) | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (findClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;") \ | |||
| STATICMETHOD (getSystemClassLoader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;") | |||
| DECLARE_JNI_CLASS (JavaClassLoader, "java/lang/ClassLoader") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (constructor, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V") | |||
| DECLARE_JNI_CLASS (AndroidDexClassLoader, "dalvik/system/DexClassLoader") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (constructor, "<init>", "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V") | |||
| DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidInMemoryDexClassLoader, "dalvik/system/InMemoryDexClassLoader", 26) | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| struct SystemJavaClassComparator | |||
| { | |||
| static int compareElements (JNIClassBase* first, JNIClassBase* second) | |||
| { | |||
| auto isSysClassA = isSystemClass (first); | |||
| auto isSysClassB = isSystemClass (second); | |||
| if ((! isSysClassA) && (! isSysClassB)) | |||
| { | |||
| return DefaultElementComparator<bool>::compareElements (first != nullptr ? first->byteCode != nullptr : false, | |||
| second != nullptr ? second->byteCode != nullptr : false); | |||
| } | |||
| return DefaultElementComparator<bool>::compareElements (isSystemClass (first), | |||
| isSystemClass (second)); | |||
| } | |||
| static bool isSystemClass (JNIClassBase* cls) | |||
| { | |||
| if (cls == nullptr) | |||
| return false; | |||
| String path (cls->getClassPath()); | |||
| return path.startsWith ("java/") | |||
| || path.startsWith ("android/") | |||
| || path.startsWith ("dalvik/"); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| JNIClassBase::JNIClassBase (const char* cp, int classMinSDK, const void* bc, size_t n) | |||
| : classPath (cp), byteCode (bc), byteCodeSize (n), minSDK (classMinSDK), classRef (nullptr) | |||
| { | |||
| SystemJavaClassComparator comparator; | |||
| getClasses().addSorted (comparator, this); | |||
| } | |||
| JNIClassBase::~JNIClassBase() | |||
| { | |||
| getClasses().removeFirstMatchingValue (this); | |||
| } | |||
| Array<JNIClassBase*>& JNIClassBase::getClasses() | |||
| { | |||
| static Array<JNIClassBase*> classes; | |||
| return classes; | |||
| } | |||
| // Get code cache directory without yet having a context object | |||
| static File getCodeCacheDirectory() | |||
| { | |||
| int pid = getpid(); | |||
| File cmdline("/proc/" + String(pid) + "/cmdline"); | |||
| auto bundleId = cmdline.loadFileAsString().trimStart().trimEnd(); | |||
| if (bundleId.isEmpty()) | |||
| return {}; | |||
| return File("/data/data/" + bundleId + "/code_cache"); | |||
| } | |||
| void JNIClassBase::initialise (JNIEnv* env) | |||
| { | |||
| auto sdkVersion = getAndroidSDKVersion(); | |||
| if (sdkVersion >= minSDK) | |||
| { | |||
| LocalRef<jstring> classNameAndPackage (javaString (String (classPath).replaceCharacter (L'/', L'.'))); | |||
| static Array<GlobalRef> byteCodeLoaders; | |||
| if (! SystemJavaClassComparator::isSystemClass(this)) | |||
| { | |||
| LocalRef<jobject> defaultClassLoader (env->CallStaticObjectMethod (JavaClassLoader, JavaClassLoader.getSystemClassLoader)); | |||
| tryLoadingClassWithClassLoader (env, defaultClassLoader.get()); | |||
| if (classRef == nullptr) | |||
| { | |||
| for (auto& byteCodeLoader : byteCodeLoaders) | |||
| { | |||
| tryLoadingClassWithClassLoader (env, byteCodeLoader.get()); | |||
| if (classRef != nullptr) | |||
| break; | |||
| } | |||
| // fallback by trying to load the class from bytecode | |||
| if (byteCode != nullptr) | |||
| { | |||
| LocalRef<jobject> byteCodeClassLoader; | |||
| MemoryOutputStream uncompressedByteCode; | |||
| { | |||
| MemoryInputStream rawGZipData (byteCode, byteCodeSize, false); | |||
| GZIPDecompressorInputStream gzipStream (&rawGZipData, false, GZIPDecompressorInputStream::gzipFormat); | |||
| uncompressedByteCode.writeFromInputStream (gzipStream, -1); | |||
| } | |||
| if (sdkVersion >= 26) | |||
| { | |||
| LocalRef<jbyteArray> byteArray (env->NewByteArray ((jsize) uncompressedByteCode.getDataSize())); | |||
| jboolean isCopy; | |||
| auto* dst = env->GetByteArrayElements (byteArray.get(), &isCopy); | |||
| memcpy (dst, uncompressedByteCode.getData(), uncompressedByteCode.getDataSize()); | |||
| env->ReleaseByteArrayElements (byteArray.get(), dst, 0); | |||
| LocalRef<jobject> byteBuffer (env->CallStaticObjectMethod (JavaByteBuffer, JavaByteBuffer.wrap, byteArray.get())); | |||
| byteCodeClassLoader = LocalRef<jobject> (env->NewObject (AndroidInMemoryDexClassLoader, | |||
| AndroidInMemoryDexClassLoader.constructor, | |||
| byteBuffer.get(), defaultClassLoader.get())); | |||
| } | |||
| else if (uncompressedByteCode.getDataSize() >= 32) | |||
| { | |||
| auto codeCacheDir = getCodeCacheDirectory(); | |||
| // The dex file has an embedded 20-byte long SHA-1 signature at offset 12 | |||
| auto fileName = String::toHexString ((char*)uncompressedByteCode.getData() + 12, 20, 0) + ".dex"; | |||
| auto dexFile = codeCacheDir.getChildFile (fileName); | |||
| auto optimizedDirectory = codeCacheDir.getChildFile ("optimized_cache"); | |||
| optimizedDirectory.createDirectory(); | |||
| if (dexFile.replaceWithData (uncompressedByteCode.getData(), uncompressedByteCode.getDataSize())) | |||
| { | |||
| byteCodeClassLoader = LocalRef<jobject> (env->NewObject (AndroidDexClassLoader, | |||
| AndroidDexClassLoader.constructor, | |||
| javaString (dexFile.getFullPathName()).get(), | |||
| javaString (optimizedDirectory.getFullPathName()).get(), | |||
| nullptr, | |||
| defaultClassLoader.get())); | |||
| } | |||
| else | |||
| { | |||
| // can't write to cache folder | |||
| jassertfalse; | |||
| } | |||
| } | |||
| if (byteCodeClassLoader != nullptr) | |||
| { | |||
| tryLoadingClassWithClassLoader (env, byteCodeClassLoader.get()); | |||
| byteCodeLoaders.add (GlobalRef(byteCodeClassLoader)); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| if (classRef == nullptr) | |||
| classRef = (jclass) env->NewGlobalRef (LocalRef<jobject> (env->FindClass (classPath))); | |||
| jassert (classRef != nullptr); | |||
| initialiseFields (env); | |||
| } | |||
| } | |||
| void JNIClassBase::tryLoadingClassWithClassLoader (JNIEnv* env, jobject classLoader) | |||
| { | |||
| LocalRef<jstring> classNameAndPackage (javaString (String (classPath).replaceCharacter (L'/', L'.'))); | |||
| // Android SDK <= 19 has a bug where the class loader might throw an exception but still return | |||
| // a non-nullptr. So don't assign the result of this call to a jobject just yet... | |||
| auto classObj = env->CallObjectMethod (classLoader, JavaClassLoader.findClass, classNameAndPackage.get()); | |||
| if (jthrowable exception = env->ExceptionOccurred ()) | |||
| { | |||
| env->ExceptionClear(); | |||
| classObj = nullptr; | |||
| } | |||
| // later versions of Android don't throw at all, so re-check the object | |||
| if (classObj != nullptr) | |||
| classRef = (jclass) env->NewGlobalRef (LocalRef<jobject> (classObj)); | |||
| } | |||
| void JNIClassBase::release (JNIEnv* env) | |||
| { | |||
| if (classRef != nullptr) | |||
| env->DeleteGlobalRef (classRef); | |||
| } | |||
| void JNIClassBase::initialiseAllClasses (JNIEnv* env) | |||
| { | |||
| const Array<JNIClassBase*>& classes = getClasses(); | |||
| for (int i = classes.size(); --i >= 0;) | |||
| classes.getUnchecked(i)->initialise (env); | |||
| } | |||
| void JNIClassBase::releaseAllClasses (JNIEnv* env) | |||
| { | |||
| const Array<JNIClassBase*>& classes = getClasses(); | |||
| for (int i = classes.size(); --i >= 0;) | |||
| classes.getUnchecked(i)->release (env); | |||
| } | |||
| jmethodID JNIClassBase::resolveMethod (JNIEnv* env, const char* methodName, const char* params) | |||
| { | |||
| jmethodID m = env->GetMethodID (classRef, methodName, params); | |||
| jassert (m != nullptr); | |||
| return m; | |||
| } | |||
| jmethodID JNIClassBase::resolveStaticMethod (JNIEnv* env, const char* methodName, const char* params) | |||
| { | |||
| jmethodID m = env->GetStaticMethodID (classRef, methodName, params); | |||
| jassert (m != nullptr); | |||
| return m; | |||
| } | |||
| jfieldID JNIClassBase::resolveField (JNIEnv* env, const char* fieldName, const char* signature) | |||
| { | |||
| jfieldID f = env->GetFieldID (classRef, fieldName, signature); | |||
| jassert (f != nullptr); | |||
| return f; | |||
| } | |||
| jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, const char* signature) | |||
| { | |||
| jfieldID f = env->GetStaticFieldID (classRef, fieldName, signature); | |||
| jassert (f != nullptr); | |||
| return f; | |||
| } | |||
| void JNIClassBase::resolveCallbacks (JNIEnv* env, const Array<JNINativeMethod>& nativeCallbacks) | |||
| { | |||
| if (nativeCallbacks.size() > 0) | |||
| env->RegisterNatives (classRef, nativeCallbacks.begin(), (jint) nativeCallbacks.size()); | |||
| } | |||
| //============================================================================== | |||
| LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer, | |||
| const StringArray& interfaceNames, | |||
| LocalRef<jobject> subclass) | |||
| { | |||
| auto* env = getEnv(); | |||
| implementer->javaSubClass = GlobalRef (subclass); | |||
| // you need to override at least one interface | |||
| jassert (interfaceNames.size() > 0); | |||
| auto classArray = LocalRef<jobject> (env->NewObjectArray (interfaceNames.size(), JavaClass, nullptr)); | |||
| LocalRef<jobject> classLoader; | |||
| for (auto i = 0; i < interfaceNames.size(); ++i) | |||
| { | |||
| auto aClass = LocalRef<jobject> (env->FindClass (interfaceNames[i].toRawUTF8())); | |||
| if (aClass != nullptr) | |||
| { | |||
| if (i == 0) | |||
| classLoader = LocalRef<jobject> (env->CallObjectMethod (aClass, JavaClass.getClassLoader)); | |||
| env->SetObjectArrayElement ((jobjectArray) classArray.get(), i, aClass); | |||
| } | |||
| else | |||
| { | |||
| // interface class not found | |||
| jassertfalse; | |||
| } | |||
| } | |||
| auto invocationHandler = LocalRef<jobject> (env->NewObject (JuceInvocationHandler, JuceInvocationHandler.constructor, | |||
| reinterpret_cast<jlong> (implementer))); | |||
| // CreateJavaInterface() is expected to be called just once for a given implementer | |||
| jassert (implementer->invocationHandler == nullptr); | |||
| implementer->invocationHandler = GlobalRef (invocationHandler); | |||
| return LocalRef<jobject> (env->CallStaticObjectMethod (JavaProxy, JavaProxy.newProxyInstance, | |||
| classLoader.get(), classArray.get(), | |||
| invocationHandler.get())); | |||
| } | |||
| LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer, | |||
| const StringArray& interfaceNames) | |||
| { | |||
| return CreateJavaInterface (implementer, interfaceNames, | |||
| LocalRef<jobject> (getEnv()->NewObject (JavaObject, | |||
| JavaObject.constructor))); | |||
| } | |||
| LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer, | |||
| const String& interfaceName) | |||
| { | |||
| return CreateJavaInterface (implementer, StringArray (interfaceName)); | |||
| } | |||
| AndroidInterfaceImplementer::~AndroidInterfaceImplementer() | |||
| { | |||
| clear(); | |||
| } | |||
| void AndroidInterfaceImplementer::clear() | |||
| { | |||
| if (invocationHandler != nullptr) | |||
| getEnv()->CallVoidMethod (invocationHandler, | |||
| JuceInvocationHandler.clear); | |||
| } | |||
| jobject AndroidInterfaceImplementer::invoke (jobject /*proxy*/, jobject method, jobjectArray args) | |||
| { | |||
| auto* env = getEnv(); | |||
| return env->CallObjectMethod (method, JavaMethod.invoke, javaSubClass.get(), args); | |||
| } | |||
| jobject juce_invokeImplementer (JNIEnv*, jobject /*object*/, jlong host, jobject proxy, | |||
| jobject method, jobjectArray args) | |||
| { | |||
| if (auto* myself = reinterpret_cast<AndroidInterfaceImplementer*> (host)) | |||
| return myself->invoke (proxy, method, args); | |||
| return nullptr; | |||
| } | |||
| void juce_dispatchDelete (JNIEnv*, jobject /*object*/, jlong host) | |||
| { | |||
| if (auto* myself = reinterpret_cast<AndroidInterfaceImplementer*> (host)) | |||
| delete myself; | |||
| } | |||
| //============================================================================== | |||
| jobject ActivityLifecycleCallbacks::invoke (jobject proxy, jobject method, jobjectArray args) | |||
| { | |||
| auto* env = getEnv(); | |||
| auto methodName = juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName)); | |||
| auto activity = env->GetArrayLength (args) > 0 ? env->GetObjectArrayElement (args, 0) : (jobject) nullptr; | |||
| auto bundle = env->GetArrayLength (args) > 1 ? env->GetObjectArrayElement (args, 1) : (jobject) nullptr; | |||
| if (methodName == "onActivityPreCreated") { onActivityPreCreated (activity, bundle); return nullptr; } | |||
| else if (methodName == "onActivityPreDestroyed") { onActivityPreDestroyed (activity); return nullptr; } | |||
| else if (methodName == "onActivityPrePaused") { onActivityPrePaused (activity); return nullptr; } | |||
| else if (methodName == "onActivityPreResumed") { onActivityPreResumed (activity); return nullptr; } | |||
| else if (methodName == "onActivityPreSaveInstanceState") { onActivityPreSaveInstanceState (activity, bundle); return nullptr; } | |||
| else if (methodName == "onActivityPreStarted") { onActivityPreStarted (activity); return nullptr; } | |||
| else if (methodName == "onActivityPreStopped") { onActivityPreStopped (activity); return nullptr; } | |||
| else if (methodName == "onActivityCreated") { onActivityCreated (activity, bundle); return nullptr; } | |||
| else if (methodName == "onActivityDestroyed") { onActivityDestroyed (activity); return nullptr; } | |||
| else if (methodName == "onActivityPaused") { onActivityPaused (activity); return nullptr; } | |||
| else if (methodName == "onActivityResumed") { onActivityResumed (activity); return nullptr; } | |||
| else if (methodName == "onActivitySaveInstanceState") { onActivitySaveInstanceState (activity, bundle); return nullptr; } | |||
| else if (methodName == "onActivityStarted") { onActivityStarted (activity); return nullptr; } | |||
| else if (methodName == "onActivityStopped") { onActivityStopped (activity); return nullptr; } | |||
| else if (methodName == "onActivityPostCreated") { onActivityPostCreated (activity, bundle); return nullptr; } | |||
| else if (methodName == "onActivityPostDestroyed") { onActivityPostDestroyed (activity); return nullptr; } | |||
| else if (methodName == "onActivityPostPaused") { onActivityPostPaused (activity); return nullptr; } | |||
| else if (methodName == "onActivityPostResumed") { onActivityPostResumed (activity); return nullptr; } | |||
| else if (methodName == "onActivityPostSaveInstanceState") { onActivityPostSaveInstanceState (activity, bundle); return nullptr; } | |||
| else if (methodName == "onActivityPostStarted") { onActivityPostStarted (activity); return nullptr; } | |||
| else if (methodName == "onActivityPostStopped") { onActivityPostStopped (activity); return nullptr; } | |||
| return AndroidInterfaceImplementer::invoke (proxy, method, args); | |||
| } | |||
| //============================================================================== | |||
| int getAndroidSDKVersion() | |||
| { | |||
| // this is used so often that we need to cache this | |||
| static int sdkVersion = [] | |||
| { | |||
| // don't use any jni helpers as they might not have been initialised yet | |||
| // when this method is used | |||
| auto* env = getEnv(); | |||
| auto buildVersion = env->FindClass ("android/os/Build$VERSION"); | |||
| jassert (buildVersion != nullptr); | |||
| auto sdkVersionField = env->GetStaticFieldID (buildVersion, "SDK_INT", "I"); | |||
| jassert (sdkVersionField != nullptr); | |||
| return env->GetStaticIntField (buildVersion, sdkVersionField); | |||
| }(); | |||
| return sdkVersion; | |||
| } | |||
| bool isPermissionDeclaredInManifest (const String& requestedPermission) | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> pkgManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager)); | |||
| LocalRef<jobject> pkgName (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageName)); | |||
| LocalRef<jobject> pkgInfo (env->CallObjectMethod (pkgManager.get(), AndroidPackageManager.getPackageInfo, | |||
| pkgName.get(), 0x00001000 /* PERMISSIONS */)); | |||
| LocalRef<jobjectArray> permissions ((jobjectArray) env->GetObjectField (pkgInfo.get(), AndroidPackageInfo.requestedPermissions)); | |||
| int n = env->GetArrayLength (permissions); | |||
| for (int i = 0; i < n; ++i) | |||
| { | |||
| LocalRef<jstring> jstr ((jstring) env->GetObjectArrayElement (permissions, i)); | |||
| String permissionId (juceString (jstr)); | |||
| if (permissionId == requestedPermission) | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| // This byte-code is generated from native/java/com/rmsl/juce/FragmentOverlay.java with min sdk version 16 | |||
| // See juce_core/native/java/README.txt on how to generate this byte-code. | |||
| static const uint8 javaFragmentOverlay[] = | |||
| {31,139,8,8,26,116,161,94,0,3,106,97,118,97,70,114,97,103,109,101,110,116,79,118,101,114,108,97,121,46,100,101,120,0,133,149, | |||
| 77,136,28,69,20,199,255,53,253,181,159,179,147,221,184,140,235,198,140,43,70,197,224,172,104,36,56,99,216,152,32,204,100,226,71, | |||
| 54,204,97,227,165,153,105,39,189,206,118,79,186,123,150,4,20,53,4,146,131,8,6,252,130,28,114,80,65,48,8,226,65,196,83,8,66,64, | |||
| 65,146,75,252,184,152,179,160,160,4,17,5,255,175,187,58,27,150,136,195,252,250,189,122,245,234,189,170,215,213,85,93,239,248, | |||
| 216,226,163,187,96,79,85,156,198,103,91,86,175,30,189,252,253,193,79,203,15,189,242,199,245,246,129,179,245,238,53,27,24,0,56, | |||
| 222,126,108,26,250,183,155,182,7,145,217,199,200,86,149,201,58,37,255,248,156,143,18,229,87,186,93,47,0,47,155,192,11,148,87, | |||
| 12,224,7,242,27,249,157,220,32,127,145,127,200,93,244,217,69,154,228,37,242,42,57,73,206,144,55,201,89,242,62,57,79,62,36,31, | |||
| 147,11,228,34,185,76,174,144,107,228,103,242,43,249,147,216,22,80,38,139,228,9,210,36,47,146,51,228,45,114,158,92,32,95,146,175, | |||
| 201,183,132,211,4,167,3,46,19,14,25,33,163,122,173,227,100,70,214,76,24,62,93,223,41,58,91,186,13,237,227,104,125,66,235,111, | |||
| 208,103,82,235,239,81,47,106,253,3,234,83,90,255,196,200,234,38,250,23,212,183,104,253,18,245,105,173,127,147,230,82,152,133, | |||
| 204,179,144,230,40,112,118,119,235,246,130,158,199,28,196,47,235,23,121,135,150,101,100,227,239,76,165,129,249,84,218,216,150, | |||
| 202,44,142,197,21,111,79,165,137,74,42,29,220,163,199,47,164,210,194,189,200,214,172,0,157,37,211,229,55,98,103,210,160,69,108, | |||
| 87,173,172,134,131,146,248,202,204,87,42,82,129,188,255,71,221,159,247,4,37,155,126,69,214,209,76,223,193,117,43,91,255,50,55, | |||
| 220,44,147,61,194,48,187,217,187,28,177,38,199,212,41,245,182,243,209,186,61,202,88,69,200,72,89,255,47,28,35,107,10,43,10,135, | |||
| 25,209,161,117,2,115,106,22,65,197,96,149,199,177,178,196,136,75,183,70,116,210,246,96,137,121,159,47,166,239,49,203,127,227, | |||
| 127,242,59,105,254,201,52,191,212,86,246,142,12,148,247,23,150,100,62,183,205,179,56,5,83,21,117,221,108,189,231,160,101,166, | |||
| 143,166,117,81,154,124,191,73,111,174,139,71,33,213,77,237,99,215,253,192,79,246,96,235,211,145,219,91,243,130,228,217,117,47, | |||
| 234,187,39,30,94,117,215,93,168,6,84,19,133,102,11,170,133,249,150,27,116,163,208,239,86,221,193,160,186,223,119,251,97,47,31, | |||
| 85,67,249,102,111,39,12,18,154,170,141,84,212,48,115,179,39,140,171,79,13,131,110,223,171,97,123,171,19,174,85,163,181,184,95, | |||
| 93,29,118,188,234,166,244,53,76,183,100,6,213,190,27,244,170,203,73,228,7,189,26,84,27,102,187,209,104,201,179,213,66,161,221, | |||
| 132,213,110,138,65,4,45,70,187,41,102,114,164,129,153,35,183,9,97,117,250,97,236,193,233,12,6,135,143,250,49,204,174,155,184, | |||
| 112,186,126,188,230,199,49,38,122,94,178,55,234,13,101,42,49,28,182,90,97,208,163,57,114,131,228,144,23,15,251,52,151,194,96, | |||
| 111,39,241,215,253,228,68,102,194,236,102,203,51,46,91,30,70,194,96,95,228,185,137,135,98,174,233,158,185,48,56,228,29,27,122, | |||
| 113,242,156,23,73,106,63,12,98,29,173,242,223,125,122,180,19,6,203,137,27,37,152,212,138,182,143,15,54,6,96,60,202,130,236,11, | |||
| 187,30,198,162,116,124,170,91,113,34,83,50,19,41,192,54,56,197,194,206,26,246,83,30,168,99,143,177,227,254,178,83,60,253,14,22, | |||
| 212,3,78,177,126,233,244,10,30,55,118,220,55,79,219,187,216,73,167,39,105,129,178,248,121,155,175,191,102,254,100,90,39,121, | |||
| 146,220,130,165,254,54,13,117,206,42,168,239,200,57,155,210,158,220,244,205,139,204,239,4,217,143,249,189,96,96,227,110,200,247, | |||
| 172,220,15,114,118,228,119,132,141,141,123,66,85,178,182,220,21,170,148,157,11,114,190,22,42,89,124,185,63,12,237,35,231,138, | |||
| 28,80,42,63,115,74,153,46,247,211,191,81,33,150,205,216,6,0,0,0,0}; | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (construct, "<init>", "()V") \ | |||
| METHOD (close, "close", "()V") \ | |||
| CALLBACK (FragmentOverlay::onActivityResultNative, "onActivityResultNative", "(JIILandroid/content/Intent;)V") \ | |||
| CALLBACK (FragmentOverlay::onCreateNative, "onCreateNative", "(JLandroid/os/Bundle;)V") \ | |||
| CALLBACK (FragmentOverlay::onStartNative, "onStartNative", "(J)V") \ | |||
| CALLBACK (FragmentOverlay::onRequestPermissionsResultNative, "onRequestPermissionsResultNative", "(JI[Ljava/lang/String;[I)V") | |||
| DECLARE_JNI_CLASS_WITH_BYTECODE (JuceFragmentOverlay, "com/rmsl/juce/FragmentOverlay", 16, javaFragmentOverlay, sizeof(javaFragmentOverlay)) | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (show, "show", "(Landroid/app/FragmentManager;Ljava/lang/String;)V") | |||
| DECLARE_JNI_CLASS (AndroidDialogFragment, "android/app/DialogFragment") | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| FragmentOverlay::FragmentOverlay() | |||
| : native (LocalRef<jobject> (getEnv()->NewObject (JuceFragmentOverlay, JuceFragmentOverlay.construct))) | |||
| {} | |||
| FragmentOverlay::~FragmentOverlay() | |||
| { | |||
| auto* env = getEnv(); | |||
| env->CallVoidMethod (native.get(), JuceFragmentOverlay.close); | |||
| } | |||
| void FragmentOverlay::open() | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> bundle (env->NewObject (AndroidBundle, AndroidBundle.constructor)); | |||
| env->CallVoidMethod (bundle.get(), AndroidBundle.putLong, javaString ("cppThis").get(), (jlong) this); | |||
| env->CallVoidMethod (native.get(), AndroidFragment.setArguments, bundle.get()); | |||
| LocalRef<jobject> fm (env->CallObjectMethod (getCurrentActivity().get(), AndroidActivity.getFragmentManager)); | |||
| env->CallVoidMethod (native.get(), AndroidDialogFragment.show, fm.get(), javaString ("FragmentOverlay").get()); | |||
| } | |||
| void FragmentOverlay::onActivityResultNative (JNIEnv* env, jobject, jlong host, | |||
| jint requestCode, jint resultCode, jobject data) | |||
| { | |||
| if (auto* myself = reinterpret_cast<FragmentOverlay*> (host)) | |||
| myself->onActivityResult (requestCode, resultCode, LocalRef<jobject> (env->NewLocalRef (data))); | |||
| } | |||
| void FragmentOverlay::onCreateNative (JNIEnv* env, jobject, jlong host, jobject bundle) | |||
| { | |||
| if (auto* myself = reinterpret_cast<FragmentOverlay*> (host)) | |||
| myself->onCreated (LocalRef<jobject> (env->NewLocalRef (bundle))); | |||
| } | |||
| void FragmentOverlay::onStartNative (JNIEnv*, jobject, jlong host) | |||
| { | |||
| if (auto* myself = reinterpret_cast<FragmentOverlay*> (host)) | |||
| myself->onStart(); | |||
| } | |||
| void FragmentOverlay::onRequestPermissionsResultNative (JNIEnv* env, jobject, jlong host, jint requestCode, | |||
| jobjectArray jPermissions, jintArray jGrantResults) | |||
| { | |||
| if (auto* myself = reinterpret_cast<FragmentOverlay*> (host)) | |||
| { | |||
| Array<int> grantResults; | |||
| int n = (jGrantResults != nullptr ? env->GetArrayLength (jGrantResults) : 0); | |||
| if (n > 0) | |||
| { | |||
| auto* data = env->GetIntArrayElements (jGrantResults, nullptr); | |||
| for (int i = 0; i < n; ++i) | |||
| grantResults.add (data[i]); | |||
| env->ReleaseIntArrayElements (jGrantResults, data, 0); | |||
| } | |||
| myself->onRequestPermissionsResult (requestCode, | |||
| javaStringArrayToJuce (LocalRef<jobjectArray> (jPermissions)), | |||
| grantResults); | |||
| } | |||
| } | |||
| jobject FragmentOverlay::getNativeHandle() | |||
| { | |||
| return native.get(); | |||
| } | |||
| //============================================================================== | |||
| class ActivityLauncher : public FragmentOverlay | |||
| { | |||
| public: | |||
| ActivityLauncher (const LocalRef<jobject>& intentToUse, | |||
| int requestCodeToUse, | |||
| std::function<void (int, int, LocalRef<jobject>)> && callbackToUse) | |||
| : intent (intentToUse), requestCode (requestCodeToUse), callback (std::move (callbackToUse)) | |||
| {} | |||
| void onStart() override | |||
| { | |||
| getEnv()->CallVoidMethod (getNativeHandle(), AndroidFragment.startActivityForResult, | |||
| intent.get(), requestCode); | |||
| } | |||
| void onActivityResult (int activityRequestCode, int resultCode, LocalRef<jobject> data) override | |||
| { | |||
| if (callback) | |||
| callback (activityRequestCode, resultCode, std::move (data)); | |||
| getEnv()->CallVoidMethod (getNativeHandle(), JuceFragmentOverlay.close); | |||
| delete this; | |||
| } | |||
| private: | |||
| GlobalRef intent; | |||
| int requestCode; | |||
| std::function<void (int, int, LocalRef<jobject>)> callback; | |||
| }; | |||
| void startAndroidActivityForResult (const LocalRef<jobject>& intent, int requestCode, | |||
| std::function<void (int, int, LocalRef<jobject>)> && callback) | |||
| { | |||
| auto* activityLauncher = new ActivityLauncher (intent, requestCode, std::move (callback)); | |||
| activityLauncher->open(); | |||
| } | |||
| //============================================================================== | |||
| bool androidHasSystemFeature (const String& property) | |||
| { | |||
| LocalRef<jobject> appContext (getAppContext()); | |||
| if (appContext != nullptr) | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> packageManager (env->CallObjectMethod (appContext.get(), AndroidContext.getPackageManager)); | |||
| if (packageManager != nullptr) | |||
| return env->CallBooleanMethod (packageManager.get(), | |||
| AndroidPackageManager.hasSystemFeature, | |||
| javaString (property).get()) != 0; | |||
| } | |||
| // unable to get app's context | |||
| jassertfalse; | |||
| return false; | |||
| } | |||
| String audioManagerGetProperty (const String& property) | |||
| { | |||
| if (getAndroidSDKVersion() >= 17) | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> audioManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService, | |||
| javaString ("audio").get())); | |||
| if (audioManager != nullptr) | |||
| { | |||
| LocalRef<jstring> jProperty (javaString (property)); | |||
| auto methodID = env->GetMethodID (AndroidAudioManager, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"); | |||
| if (methodID != nullptr) | |||
| return juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (audioManager.get(), | |||
| methodID, | |||
| javaString (property).get()))); | |||
| } | |||
| } | |||
| return {}; | |||
| } | |||
| } | |||
| @@ -1,44 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| void Logger::outputDebugString (const String& text) | |||
| { | |||
| char* data = text.toUTF8().getAddress(); | |||
| const size_t length = CharPointer_UTF8::getBytesRequiredFor (text.getCharPointer()); | |||
| const size_t chunkSize = 1023; | |||
| size_t position = 0; | |||
| size_t numToRead = jmin (chunkSize, length); | |||
| while (numToRead > 0) | |||
| { | |||
| __android_log_print (ANDROID_LOG_INFO, "JUCE", "%s", data + position); | |||
| position += numToRead; | |||
| numToRead = jmin (chunkSize, length - position); | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -1,656 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| // This byte-code is generated from native/java/com/rmsl/juce/JuceHTTPStream.java with min sdk version 16 | |||
| // See juce_core/native/java/README.txt on how to generate this byte-code. | |||
| static const uint8 javaJuceHttpStream[] = | |||
| {31,139,8,8,71,116,161,94,0,3,106,97,118,97,74,117,99,101,72,116,116,112,83,116,114,101,97,109,46,100,101,120,0,125,154,11,124, | |||
| 84,87,157,199,255,231,222,185,119,30,153,153,220,76,30,147,132,60,38,33,132,4,18,38,80,104,41,9,148,242,42,144,80,40,12,84,8, | |||
| 91,29,50,23,50,48,185,19,102,238,64,168,180,133,62,233,67,75,223,184,86,173,72,45,186,85,171,86,173,182,174,109,105,93,31,181, | |||
| 85,87,119,171,187,186,186,91,171,246,211,221,118,235,171,171,31,55,251,59,143,201,76,218,44,132,239,253,255,207,255,252,207, | |||
| 185,231,241,63,143,153,36,101,79,4,250,46,88,66,151,94,114,238,181,147,179,244,212,201,205,225,159,190,152,158,247,165,219,39, | |||
| 126,178,240,215,230,173,135,207,118,18,141,19,209,196,142,197,17,82,255,94,153,67,244,35,146,246,69,224,105,157,40,14,121,194, | |||
| 67,84,15,249,148,73,116,5,228,81,47,17,178,200,19,32,90,211,68,148,130,124,171,150,232,15,224,109,240,23,240,191,64,171,35,50, | |||
| 64,8,84,129,40,232,6,23,131,45,96,27,184,18,236,6,123,128,13,174,6,215,128,227,224,102,112,27,184,19,156,2,103,192,89,240,44, | |||
| 120,5,52,70,137,150,129,171,192,13,224,19,224,155,224,87,128,161,193,49,112,17,184,28,236,5,199,192,3,224,81,240,34,120,5,188, | |||
| 13,194,13,68,237,96,0,92,9,14,128,235,193,67,224,49,240,29,240,11,240,22,240,55,18,189,7,164,193,213,224,111,193,147,224,183, | |||
| 160,102,22,234,0,87,129,163,224,195,224,39,96,18,244,96,156,222,3,70,192,94,176,31,56,32,15,222,15,142,129,59,192,73,112,15,120, | |||
| 0,124,24,124,12,124,2,60,1,158,2,223,2,63,6,63,7,175,130,55,192,159,192,95,129,209,76,20,4,81,16,3,61,96,13,216,1,70,193,213, | |||
| 224,14,240,17,240,48,120,18,156,3,47,129,87,193,95,128,217,130,62,2,11,52,130,14,48,31,44,2,23,129,77,224,111,128,3,174,5,55, | |||
| 131,7,192,25,240,37,240,60,248,30,248,103,240,91,240,22,152,4,190,86,162,58,208,9,250,193,101,96,35,216,6,222,7,246,131,195,224, | |||
| 24,56,1,238,3,31,3,15,131,207,128,47,130,175,130,23,193,207,192,107,224,45,240,103,64,49,244,29,84,130,57,160,31,236,0,123,193, | |||
| 1,48,14,14,129,163,224,102,112,18,124,12,60,12,62,11,190,1,126,12,126,6,254,29,252,26,252,25,120,219,136,102,131,62,176,18, | |||
| 108,5,123,193,24,112,193,81,112,2,124,8,60,2,62,7,190,6,190,13,126,14,126,7,244,118,196,5,104,4,237,160,23,44,7,151,129,4,200, | |||
| 128,2,184,6,220,6,238,2,167,64,16,221,178,0,194,138,16,62,132,233,37,76,15,97,40,73,117,153,80,61,193,149,102,131,14,128,229, | |||
| 75,88,214,52,23,116,129,110,48,15,204,7,61,160,23,44,32,185,166,251,192,66,181,206,47,0,139,193,18,112,33,184,8,44,5,23,131,126, | |||
| 48,0,86,128,75,192,74,112,41,88,5,214,128,117,224,50,176,30,108,4,91,193,118,176,3,92,73,178,31,197,127,33,37,151,98,111,8, | |||
| 43,125,101,153,190,30,122,165,210,183,212,202,254,51,149,230,251,143,1,118,195,30,41,171,151,235,197,242,13,74,119,149,79,177, | |||
| 174,90,229,183,84,217,107,149,189,90,233,71,149,61,90,102,143,42,123,141,210,111,132,94,167,244,59,148,189,94,217,107,149,190, | |||
| 84,233,13,101,58,159,183,123,107,101,57,174,63,168,222,213,92,214,254,150,50,189,181,76,111,47,211,103,151,245,133,207,239,163, | |||
| 170,126,62,199,93,170,206,121,202,135,207,67,175,210,7,149,206,251,178,89,233,143,67,31,82,250,83,101,122,95,153,206,219,191, | |||
| 73,233,207,65,223,162,116,62,254,151,43,253,133,50,159,127,173,149,103,67,175,26,255,98,61,175,212,202,152,88,160,218,179,77, | |||
| 233,175,195,158,80,186,171,250,178,80,189,87,199,76,127,139,184,92,64,223,135,244,96,228,108,33,107,105,159,144,61,148,22,50, | |||
| 76,31,20,178,155,190,65,60,62,154,40,37,164,244,51,148,159,129,17,43,8,57,159,110,20,50,74,183,40,121,171,144,178,30,3,239,187, | |||
| 155,100,156,157,22,146,209,167,132,140,211,167,133,172,167,207,10,217,71,95,23,178,248,94,172,121,37,95,0,26,85,209,125,162, | |||
| 253,109,100,10,233,165,247,10,25,20,210,131,246,152,66,182,210,231,69,185,78,145,230,237,222,169,218,57,34,100,29,237,21,210, | |||
| 164,81,101,63,40,164,159,142,8,105,208,117,74,30,87,249,39,133,212,233,81,33,91,233,51,106,28,190,76,124,205,180,139,247,132, | |||
| 176,91,112,25,70,126,82,72,143,240,175,68,250,176,144,243,232,135,34,238,42,232,128,138,191,59,69,236,181,138,114,81,140,203, | |||
| 30,37,63,64,50,182,239,23,114,1,125,77,200,74,122,70,72,75,200,122,172,168,221,66,70,104,76,72,89,174,30,35,37,165,44,95,175, | |||
| 252,27,212,123,26,176,202,118,11,25,166,239,18,223,11,103,11,123,35,242,159,36,190,158,90,201,17,210,71,87,11,217,72,239,87,233, | |||
| 163,66,6,232,90,146,235,238,152,144,81,186,94,200,24,125,81,200,110,122,92,201,47,41,251,87,132,236,160,39,132,156,67,95,37, | |||
| 190,86,229,120,53,99,87,149,178,158,206,8,41,219,213,138,113,191,73,200,32,125,148,248,222,28,162,9,226,251,115,144,174,81,242, | |||
| 6,226,235,121,22,185,196,215,114,3,221,75,124,29,55,211,211,196,247,234,22,202,8,217,68,31,34,190,166,123,233,125,66,6,69,61, | |||
| 243,213,120,204,199,143,76,119,211,23,136,239,233,210,206,229,3,66,206,167,23,85,250,37,146,119,52,34,185,182,248,30,81,5,249, | |||
| 4,54,174,196,28,105,247,148,229,247,169,252,151,145,63,166,242,121,60,51,42,237,151,60,255,109,228,223,164,242,121,253,255,132, | |||
| 131,231,103,224,213,14,233,251,39,37,249,97,164,65,248,33,173,57,210,214,168,100,183,146,171,148,28,156,195,235,210,133,254,32, | |||
| 54,61,31,228,48,18,187,53,70,227,214,44,17,161,60,151,215,247,112,187,220,35,219,89,144,182,199,24,206,41,139,18,49,162,131, | |||
| 150,87,68,184,99,245,9,57,30,171,70,137,42,54,61,111,129,144,93,111,241,182,49,241,190,207,182,203,126,58,22,183,4,69,31,77, | |||
| 252,240,188,199,219,229,121,34,219,48,204,52,26,214,12,26,214,61,52,236,209,105,216,48,197,202,225,237,50,232,165,118,121,30,38, | |||
| 250,60,168,139,239,180,1,156,159,181,148,88,168,83,51,75,244,153,104,169,143,184,140,68,114,177,245,136,145,22,38,125,47,23,165, | |||
| 52,228,123,133,116,172,94,97,49,148,197,128,165,118,170,230,45,98,52,66,40,61,23,207,146,173,235,143,72,45,148,41,38,78,8, | |||
| 194,234,213,1,151,76,157,93,186,56,231,121,207,151,8,169,163,237,23,92,23,186,112,57,86,102,128,248,40,189,218,46,247,110,222, | |||
| 103,31,70,185,7,218,38,120,14,31,247,35,85,43,236,60,197,243,230,35,181,81,164,42,176,47,241,155,64,144,29,180,46,227,227,200, | |||
| 134,143,7,104,247,109,149,52,124,123,132,134,239,8,210,206,15,212,209,240,7,107,104,248,206,106,138,252,247,206,227,81,188,217, | |||
| 162,157,199,44,188,165,138,134,143,133,104,251,13,149,148,184,49,66,137,155,130,180,21,87,255,196,45,33,242,30,247,222,125, | |||
| 200,235,87,53,122,209,115,77,221,8,170,103,203,248,75,96,110,171,68,108,122,69,140,52,194,158,231,241,225,107,70,12,44,195,58, | |||
| 118,172,1,188,35,232,107,245,53,145,247,198,86,79,19,69,124,78,236,66,156,4,78,108,49,253,30,207,139,240,108,244,189,7,90,43, | |||
| 206,129,160,22,209,219,186,150,220,16,167,117,62,77,111,244,235,176,55,211,41,10,152,203,205,118,97,115,112,57,246,81,192,183, | |||
| 228,230,70,145,142,248,29,92,17,79,153,65,198,83,47,251,77,230,196,98,40,17,244,114,79,47,60,151,251,48,139,125,23,83,141,247, | |||
| 101,93,103,93,63,72,156,13,161,214,165,104,195,210,96,132,34,245,78,108,9,215,67,60,70,23,34,166,130,134,131,15,47,63,70,42, | |||
| 138,216,170,242,132,68,84,120,208,171,16,53,250,253,232,89,13,234,159,235,147,119,156,43,249,140,178,89,251,170,113,46,4,160,31, | |||
| 153,45,239,23,253,225,90,138,132,219,81,131,247,227,236,11,222,115,222,31,177,95,121,255,232,243,90,164,249,170,200,231,143,208, | |||
| 161,128,41,198,238,226,240,127,77,86,135,251,10,93,175,133,88,132,186,222,150,99,91,37,235,109,172,16,49,30,166,7,103,243, | |||
| 25,198,216,251,78,178,72,77,68,79,60,236,167,102,35,113,186,2,173,27,132,71,64,91,138,245,209,98,176,69,149,90,36,50,30,11,226, | |||
| 28,9,106,195,167,195,148,56,93,67,17,51,241,136,23,158,139,248,136,120,7,189,154,41,173,107,52,205,92,124,253,63,82,68,155, | |||
| 94,130,251,198,81,107,80,203,89,171,149,92,195,165,153,179,86,96,133,242,183,61,71,57,235,18,232,65,214,202,218,160,95,202,35, | |||
| 5,246,55,39,115,214,42,165,191,54,153,120,164,26,35,215,128,104,237,194,188,21,189,90,181,40,230,160,27,187,110,80,107,244,121, | |||
| 132,126,61,226,164,209,175,81,177,116,171,86,67,7,99,98,39,214,74,190,84,244,213,28,107,158,104,153,19,107,199,154,43,175,185, | |||
| 209,27,128,87,19,229,208,206,139,181,179,147,165,26,99,20,241,230,98,43,145,35,61,15,90,13,178,14,171,81,140,225,206,211,81, | |||
| 234,186,51,36,198,177,155,118,79,134,212,88,44,18,185,75,174,111,147,163,233,73,124,82,90,49,18,230,160,169,121,164,87,47,188, | |||
| 102,206,151,245,45,158,156,121,46,186,105,217,100,72,107,49,230,106,33,45,113,230,221,30,30,115,238,59,230,180,155,226,40,209, | |||
| 77,61,147,168,121,85,55,121,39,249,254,225,199,174,84,45,246,80,175,184,7,243,216,233,16,123,86,181,248,172,113,29,210,95,20, | |||
| 123,79,72,156,177,252,204,255,190,242,251,23,101,255,55,149,254,79,149,126,83,236,218,140,254,42,234,173,38,31,147,118,227,186, | |||
| 208,23,216,235,236,186,206,175,176,55,24,189,206,232,13,118,109,231,77,154,48,122,49,14,252,76,249,205,108,249,121,39,162,37, | |||
| 182,249,168,89,79,92,33,163,149,81,128,45,101,136,86,221,219,134,83,226,138,0,181,99,79,31,239,99,148,48,115,177,181,248,140, | |||
| 131,29,231,138,90,228,39,240,204,89,27,196,30,212,202,194,212,245,219,16,107,209,231,50,236,78,109,93,255,33,158,191,144,103, | |||
| 105,3,124,248,186,243,225,189,115,213,62,171,81,167,86,221,61,167,167,6,186,23,173,15,97,48,248,157,161,26,31,110,34,161,67,172, | |||
| 2,253,9,88,23,91,134,176,84,90,47,135,66,204,7,219,86,102,209,133,205,255,51,201,245,4,195,124,232,107,170,152,94,205,122,25, | |||
| 191,93,46,178,106,168,154,45,80,58,198,150,197,149,14,127,118,129,208,47,176,126,170,74,87,83,163,190,1,209,216,129,27,118,128, | |||
| 189,28,102,129,146,29,177,220,55,135,22,134,3,149,23,189,241,172,242,231,243,126,129,232,175,99,45,231,210,95,244,119,250,230, | |||
| 210,47,43,131,21,197,221,245,20,181,7,161,245,245,211,29,21,220,55,136,216,88,116,247,71,167,222,27,209,133,230,65,73,171,19, | |||
| 99,20,240,56,125,179,105,161,71,88,17,43,62,92,45,155,35,210,187,124,94,42,197,104,180,68,186,105,98,18,163,29,193,104,7,184, | |||
| 165,155,198,203,218,216,11,111,158,218,174,87,146,124,79,68,74,35,40,228,86,179,78,166,189,33,196,76,53,21,79,18,110,27,70,13, | |||
| 188,254,149,147,178,102,174,175,154,36,113,63,159,139,150,222,172,246,191,7,196,60,122,232,35,144,94,104,252,222,199,119,255, | |||
| 103,85,90,167,251,176,69,158,98,215,205,122,158,221,199,72,221,98,136,110,239,144,123,232,182,88,149,248,28,91,180,223,213,33, | |||
| 99,101,107,44,138,59,187,53,117,158,61,208,33,239,75,17,236,232,126,120,123,248,59,59,228,103,67,156,137,123,176,211,106,137, | |||
| 100,152,150,50,63,113,25,209,157,149,117,116,240,134,0,181,104,203,209,158,109,123,170,232,184,231,201,85,187,32,43,9,99,166, | |||
| 97,204,88,215,239,24,201,187,149,71,173,161,70,21,151,179,166,206,255,6,22,106,226,49,203,16,157,26,125,185,67,126,119,208,142, | |||
| 18,137,131,184,127,228,176,23,231,241,78,23,183,151,66,45,29,50,121,77,137,156,104,207,193,10,138,84,59,177,33,196,85,132,13, | |||
| 195,187,69,107,55,27,17,25,243,81,151,99,245,16,191,33,201,182,68,196,141,136,137,239,39,152,248,153,131,55,200,27,234,139,101, | |||
| 125,231,223,37,242,127,26,149,164,27,144,99,121,20,242,198,128,188,51,22,191,23,224,231,215,201,128,28,211,83,144,15,169,252, | |||
| 242,187,47,207,55,85,61,126,37,249,247,7,103,149,111,135,170,175,78,201,168,122,111,81,198,85,125,113,85,167,159,228,103,157, | |||
| 184,240,232,19,159,95,162,170,236,130,178,182,149,247,193,162,26,97,55,84,155,75,229,163,194,62,79,249,241,207,160,140,228,189, | |||
| 83,182,33,42,202,52,193,210,131,222,116,43,123,179,42,23,167,226,120,48,12,111,23,225,194,198,150,145,182,44,70,108,128,204,129, | |||
| 180,147,118,87,144,182,162,159,60,43,250,187,119,144,181,58,235,56,246,136,155,206,58,49,59,151,203,230,40,12,139,107,59,110, | |||
| 239,144,237,236,115,71,169,118,77,58,63,50,229,180,181,224,56,201,61,25,155,216,6,210,54,12,145,190,97,104,3,121,240,192,126, | |||
| 184,145,170,55,22,70,236,245,137,196,150,109,110,206,78,142,45,216,159,60,148,36,54,68,26,156,116,238,163,13,161,200,16,30,222, | |||
| 161,161,93,67,67,168,32,160,20,174,107,67,187,168,126,40,233,164,114,217,116,42,238,218,19,110,60,129,199,118,55,157,201,247, | |||
| 83,108,104,36,59,22,207,141,229,51,241,253,120,77,124,250,187,58,22,246,211,226,243,123,204,216,145,126,106,57,111,169,126,106, | |||
| 31,74,37,51,135,210,7,226,73,199,201,186,73,94,56,190,214,25,201,100,243,105,103,223,234,76,50,143,182,205,62,159,207,38,219, | |||
| 29,205,166,248,139,222,237,180,1,237,201,169,74,218,102,200,223,100,143,237,81,14,54,92,154,103,112,217,150,222,231,36,221,66, | |||
| 14,93,105,156,33,59,49,154,203,30,22,69,249,108,196,211,217,248,170,194,222,189,118,206,78,109,112,198,11,110,177,151,181,83, | |||
| 217,27,54,175,157,24,177,199,121,225,105,230,114,239,186,41,243,230,130,91,102,175,151,246,76,210,217,23,95,61,154,204,109,179, | |||
| 15,22,108,103,196,158,170,72,228,148,213,95,93,102,222,128,184,219,103,231,248,76,79,55,230,114,133,113,215,78,149,21,171,41, | |||
| 247,128,131,156,201,72,153,117,243,158,253,152,232,233,158,165,57,47,247,68,219,49,73,211,219,46,109,114,160,250,169,97,134,156, | |||
| 116,38,197,179,202,43,194,72,219,201,212,244,174,138,209,151,239,108,146,102,199,118,227,235,93,119,124,251,214,161,210,218, | |||
| 235,167,112,41,23,57,83,173,81,233,114,79,213,163,2,86,133,236,187,155,69,67,172,50,235,80,58,239,78,53,67,88,54,37,199,59,214, | |||
| 58,110,238,72,63,109,154,201,60,240,238,241,120,71,125,51,120,172,192,15,85,78,175,110,186,97,155,237,242,176,47,25,176,254,70, | |||
| 10,185,28,182,151,248,234,100,38,35,118,146,214,243,231,247,83,207,255,231,128,192,130,15,31,149,178,208,232,158,217,123,237, | |||
| 132,61,82,120,135,107,231,249,92,179,8,222,220,161,52,143,221,216,249,253,242,83,115,251,78,143,117,5,190,46,139,75,111,230,220, | |||
| 126,26,56,95,246,192,121,151,45,38,160,99,230,210,50,24,215,37,71,208,64,204,250,252,153,189,16,59,99,233,145,248,165,66,172, | |||
| 202,102,51,118,18,227,50,111,102,231,76,118,228,64,62,190,213,134,158,75,58,238,16,146,253,228,135,16,83,176,140,216,14,210,118, | |||
| 96,143,223,129,61,126,7,246,120,19,15,190,215,35,177,139,2,59,202,246,249,29,187,136,237,34,109,215,70,0,57,188,10,108,160, | |||
| 234,225,25,86,165,182,219,161,64,114,100,196,206,231,59,250,250,250,168,66,234,235,50,201,125,121,242,38,83,169,28,82,100,38, | |||
| 199,199,109,39,69,222,61,201,188,189,61,151,33,115,143,24,45,242,140,32,140,200,28,17,177,66,6,223,145,109,242,99,195,31,79,230, | |||
| 236,68,150,188,234,68,160,64,233,104,160,186,146,158,200,150,78,13,178,70,48,166,174,93,90,140,69,139,156,42,62,32,69,75,233,0, | |||
| 41,90,100,74,248,84,170,17,45,14,29,153,41,123,36,155,178,169,38,101,239,77,22,50,238,180,201,227,185,25,219,181,41,144,42, | |||
| 53,165,54,53,227,169,92,61,205,44,171,33,127,42,171,154,76,204,38,131,79,223,17,242,9,129,5,10,77,197,49,25,123,211,118,38,5, | |||
| 145,41,228,71,73,223,135,204,26,60,138,11,13,47,80,93,10,195,186,150,223,22,84,186,18,233,245,120,149,157,91,199,107,200,11,135, | |||
| 178,56,37,19,233,65,251,136,112,44,63,49,168,2,134,45,56,37,197,48,240,220,173,118,126,60,235,228,49,200,24,15,94,77,2,7,89,70, | |||
| 221,69,120,53,216,13,201,7,185,35,153,41,216,100,141,38,243,171,16,145,170,141,54,66,0,150,203,113,107,160,138,81,209,162,161, | |||
| 180,99,35,82,100,34,79,65,165,36,178,219,17,9,225,81,236,196,91,249,33,149,119,87,143,165,168,122,122,90,58,5,184,81,181,151, | |||
| 165,201,155,118,82,246,196,230,189,84,145,46,235,161,47,237,168,38,85,164,243,107,39,70,147,133,188,203,91,147,206,139,113,32, | |||
| 51,157,71,63,93,158,203,165,172,217,151,86,251,55,121,246,103,211,136,131,140,236,167,135,47,53,242,56,201,49,76,169,99,31,94, | |||
| 157,28,25,181,83,114,58,183,96,149,82,136,27,75,97,88,129,100,41,152,144,192,43,132,228,75,193,227,240,209,208,157,2,26,137, | |||
| 199,170,35,46,198,163,6,218,186,108,38,147,61,108,167,182,218,169,116,14,53,73,235,84,42,145,149,14,212,48,147,85,141,95,22, | |||
| 203,174,172,33,122,22,93,213,199,241,118,15,30,11,197,115,17,249,198,139,51,204,53,119,77,210,77,82,168,168,201,138,60,34,82,189, | |||
| 57,68,58,86,48,85,230,84,24,172,87,211,86,243,14,131,44,101,194,138,229,66,122,174,128,119,231,17,175,85,120,168,246,36,210,99, | |||
| 54,111,78,5,76,107,178,50,234,168,49,207,3,51,239,242,112,145,29,41,245,158,59,78,197,98,56,207,99,49,153,42,214,98,137,180, | |||
| 8,12,121,159,163,72,201,178,37,135,113,200,185,71,200,200,143,103,210,46,132,155,204,97,206,33,220,66,158,2,82,138,144,174,44, | |||
| 233,178,15,193,188,8,32,117,84,152,249,194,158,49,212,224,135,204,139,45,144,66,238,104,249,150,83,205,147,239,220,117,74,198, | |||
| 178,109,38,12,99,249,34,172,68,122,91,249,203,60,238,104,26,113,194,159,29,125,228,119,209,85,12,211,38,44,223,41,85,197,169, | |||
| 155,149,251,49,85,184,101,203,177,142,159,3,249,189,217,220,152,157,186,188,44,2,189,216,89,68,11,204,130,35,34,185,226,80,50, | |||
| 211,81,92,9,198,33,177,76,140,195,57,68,63,105,19,125,116,55,211,190,205,200,27,166,251,249,115,114,217,0,44,60,249,184,102, | |||
| 156,99,223,100,207,179,231,240,137,219,248,7,54,111,157,55,60,208,203,255,209,211,26,28,78,44,31,76,183,238,30,28,30,188,106,88, | |||
| 27,111,94,219,118,142,122,252,187,239,101,143,178,91,217,119,217,55,216,99,236,12,187,157,121,195,218,95,180,254,137,137,35,218, | |||
| 245,215,156,17,133,123,7,6,135,88,164,146,58,217,143,144,75,59,205,221,247,176,191,99,39,216,119,132,247,43,186,231,195,172, | |||
| 121,112,224,146,3,186,118,11,91,196,152,174,127,128,177,229,39,116,243,17,198,62,57,161,179,195,245,39,116,239,15,89,125,90,59, | |||
| 212,111,48,195,171,133,251,13,115,120,193,208,130,65,221,248,36,179,6,46,49,140,22,67,51,244,86,157,242,77,3,180,210,63,85, | |||
| 253,223,179,207,177,79,176,219,240,146,222,57,172,42,138,54,247,106,21,253,109,244,54,122,187,113,249,45,90,111,143,182,179,89, | |||
| 171,56,252,196,196,181,172,209,242,48,183,189,255,57,214,80,201,234,67,43,126,201,234,171,13,198,171,12,227,216,52,136,107, | |||
| 115,79,60,126,213,247,250,180,150,31,24,164,237,233,63,215,113,191,102,245,211,29,140,221,205,188,205,168,173,182,71,27,107,254, | |||
| 105,219,93,43,209,5,202,215,180,182,24,36,180,206,86,250,52,31,222,94,221,250,60,139,183,233,161,211,140,13,245,106,207,80, | |||
| 143,97,105,151,54,155,214,173,71,244,170,175,51,198,234,171,206,233,225,179,232,180,94,249,41,198,22,156,211,253,7,234,111,215, | |||
| 43,30,100,245,187,116,223,85,45,187,244,224,71,89,203,144,174,189,200,234,227,204,178,142,60,172,133,118,26,214,74,244,221, | |||
| 103,4,91,245,64,190,73,115,54,26,1,83,51,125,102,208,180,222,207,90,106,47,106,53,3,116,171,206,39,247,131,226,121,143,120,254, | |||
| 94,211,222,251,113,12,75,155,78,15,97,172,7,39,218,123,247,107,19,205,3,244,75,62,205,127,208,105,220,88,190,127,160,117,151, | |||
| 110,30,156,53,208,96,152,116,74,103,31,225,115,167,121,217,15,88,99,80,243,105,187,60,116,138,181,104,1,164,155,140,104,111,116, | |||
| 121,212,136,118,68,189,90,5,55,96,4,139,74,149,102,66,169,55,68,17,223,41,22,173,144,101,60,209,213,170,132,72,250,163,44, | |||
| 186,56,218,25,93,143,170,100,134,79,243,203,26,60,197,170,58,73,99,26,157,140,49,252,63,118,204,243,88,173,198,190,95,203,158, | |||
| 137,61,84,71,6,99,26,50,197,15,121,142,31,243,60,85,103,176,223,212,81,149,233,245,106,44,34,126,68,30,211,166,253,136,180,9, | |||
| 255,199,234,217,137,216,203,13,90,228,205,6,214,240,251,89,44,242,66,19,139,28,107,247,85,157,156,109,84,189,221,201,170,78, | |||
| 206,101,85,103,193,83,224,177,46,86,117,166,91,254,125,4,149,125,223,192,101,241,239,183,248,119,4,197,191,225,42,126,119,193, | |||
| 255,142,139,127,79,82,252,91,46,254,189,66,241,239,185,76,42,253,77,151,110,201,223,193,241,239,98,88,76,254,254,247,49,254,29, | |||
| 74,76,250,240,223,33,50,171,244,123,69,45,38,223,203,255,6,76,87,254,252,247,120,158,24,137,223,63,241,223,17,146,42,43,126, | |||
| 247,104,201,182,242,191,55,251,63,108,101,104,241,168,38,0,0,0,0}; | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (constructor, "<init>", "()V") \ | |||
| METHOD (toString, "toString", "()Ljava/lang/String;") \ | |||
| DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer") | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;ILjava/lang/String;)Lcom/rmsl/juce/JuceHTTPStream;") \ | |||
| METHOD (connect, "connect", "()Z") \ | |||
| METHOD (release, "release", "()V") \ | |||
| METHOD (read, "read", "([BI)I") \ | |||
| METHOD (getPosition, "getPosition", "()J") \ | |||
| METHOD (getTotalLength, "getTotalLength", "()J") \ | |||
| METHOD (isExhausted, "isExhausted", "()Z") \ | |||
| METHOD (setPosition, "setPosition", "(J)Z") \ | |||
| DECLARE_JNI_CLASS_WITH_BYTECODE (HTTPStream, "com/rmsl/juce/JuceHTTPStream", 16, javaJuceHttpStream, sizeof(javaJuceHttpStream)) | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (close, "close", "()V") \ | |||
| METHOD (read, "read", "([BII)I") \ | |||
| DECLARE_JNI_CLASS (AndroidInputStream, "java/io/InputStream") | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (acquire, "acquire", "()V") \ | |||
| METHOD (release, "release", "()V") \ | |||
| DECLARE_JNI_CLASS (AndroidMulticastLock, "android/net/wifi/WifiManager$MulticastLock") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (createMulticastLock, "createMulticastLock", "(Ljava/lang/String;)Landroid/net/wifi/WifiManager$MulticastLock;") \ | |||
| DECLARE_JNI_CLASS (AndroidWifiManager, "android/net/wifi/WifiManager") | |||
| #undef JNI_CLASS_MEMBERS | |||
| static LocalRef<jobject> getMulticastLock() | |||
| { | |||
| static LocalRef<jobject> multicastLock; | |||
| static bool hasChecked = false; | |||
| if (! hasChecked) | |||
| { | |||
| hasChecked = true; | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> wifiManager (env->CallObjectMethod (getAppContext().get(), | |||
| AndroidContext.getSystemService, | |||
| javaString ("wifi").get())); | |||
| if (wifiManager != nullptr) | |||
| { | |||
| multicastLock = LocalRef<jobject> (env->CallObjectMethod (wifiManager.get(), | |||
| AndroidWifiManager.createMulticastLock, | |||
| javaString ("JUCE_MulticastLock").get())); | |||
| } | |||
| } | |||
| return multicastLock; | |||
| } | |||
| JUCE_API void JUCE_CALLTYPE acquireMulticastLock(); | |||
| JUCE_API void JUCE_CALLTYPE acquireMulticastLock() | |||
| { | |||
| auto multicastLock = getMulticastLock(); | |||
| if (multicastLock != nullptr) | |||
| getEnv()->CallVoidMethod (multicastLock.get(), AndroidMulticastLock.acquire); | |||
| } | |||
| JUCE_API void JUCE_CALLTYPE releaseMulticastLock(); | |||
| JUCE_API void JUCE_CALLTYPE releaseMulticastLock() | |||
| { | |||
| auto multicastLock = getMulticastLock(); | |||
| if (multicastLock != nullptr) | |||
| getEnv()->CallVoidMethod (multicastLock.get(), AndroidMulticastLock.release); | |||
| } | |||
| //============================================================================== | |||
| void MACAddress::findAllAddresses (Array<MACAddress>& /*result*/) | |||
| { | |||
| // TODO | |||
| } | |||
| JUCE_API bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& /*targetEmailAddress*/, | |||
| const String& /*emailSubject*/, | |||
| const String& /*bodyText*/, | |||
| const StringArray& /*filesToAttach*/) | |||
| { | |||
| // TODO | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| bool URL::isLocalFile() const | |||
| { | |||
| if (getScheme() == "file") | |||
| return true; | |||
| if (getScheme() == "content") | |||
| { | |||
| auto file = AndroidContentUriResolver::getLocalFileFromContentUri (*this); | |||
| return (file != File()); | |||
| } | |||
| return false; | |||
| } | |||
| File URL::getLocalFile() const | |||
| { | |||
| if (getScheme() == "content") | |||
| { | |||
| auto path = AndroidContentUriResolver::getLocalFileFromContentUri (*this); | |||
| // This URL does not refer to a local file | |||
| // Call URL::isLocalFile to first check if the URL | |||
| // refers to a local file. | |||
| jassert (path != File()); | |||
| return path; | |||
| } | |||
| return fileFromFileSchemeURL (*this); | |||
| } | |||
| String URL::getFileName() const | |||
| { | |||
| if (getScheme() == "content") | |||
| return AndroidContentUriResolver::getFileNameFromContentUri (*this); | |||
| return toString (false).fromLastOccurrenceOf ("/", false, true); | |||
| } | |||
| //============================================================================== | |||
| class WebInputStream::Pimpl | |||
| { | |||
| public: | |||
| enum { contentStreamCacheSize = 1024 }; | |||
| Pimpl (WebInputStream&, const URL& urlToCopy, bool addParametersToBody) | |||
| : url (urlToCopy), | |||
| isContentURL (urlToCopy.getScheme() == "content"), | |||
| addParametersToRequestBody (addParametersToBody), | |||
| hasBodyDataToSend (addParametersToRequestBody || url.hasBodyDataToSend()), | |||
| httpRequest (hasBodyDataToSend ? "POST" : "GET") | |||
| { | |||
| } | |||
| ~Pimpl() | |||
| { | |||
| cancel(); | |||
| } | |||
| void cancel() | |||
| { | |||
| if (isContentURL) | |||
| { | |||
| stream.callVoidMethod (AndroidInputStream.close); | |||
| return; | |||
| } | |||
| const ScopedLock lock (createStreamLock); | |||
| if (stream != nullptr) | |||
| { | |||
| stream.callVoidMethod (HTTPStream.release); | |||
| stream.clear(); | |||
| } | |||
| hasBeenCancelled = true; | |||
| } | |||
| bool connect (WebInputStream::Listener* /*listener*/) | |||
| { | |||
| auto* env = getEnv(); | |||
| if (isContentURL) | |||
| { | |||
| auto inputStream = AndroidContentUriResolver::getStreamForContentUri (url, true); | |||
| if (inputStream != nullptr) | |||
| { | |||
| stream = GlobalRef (inputStream); | |||
| statusCode = 200; | |||
| return true; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| String address = url.toString (! addParametersToRequestBody); | |||
| if (! address.contains ("://")) | |||
| address = "http://" + address; | |||
| MemoryBlock postData; | |||
| if (hasBodyDataToSend) | |||
| WebInputStream::createHeadersAndPostData (url, | |||
| headers, | |||
| postData, | |||
| addParametersToRequestBody); | |||
| jbyteArray postDataArray = nullptr; | |||
| if (! postData.isEmpty()) | |||
| { | |||
| postDataArray = env->NewByteArray (static_cast<jsize> (postData.getSize())); | |||
| env->SetByteArrayRegion (postDataArray, 0, static_cast<jsize> (postData.getSize()), (const jbyte*) postData.getData()); | |||
| } | |||
| LocalRef<jobject> responseHeaderBuffer (env->NewObject (StringBuffer, StringBuffer.constructor)); | |||
| // Annoyingly, the android HTTP functions will choke on this call if you try to do it on the message | |||
| // thread. You'll need to move your networking code to a background thread to keep it happy.. | |||
| jassert (Thread::getCurrentThread() != nullptr); | |||
| jintArray statusCodeArray = env->NewIntArray (1); | |||
| jassert (statusCodeArray != nullptr); | |||
| { | |||
| const ScopedLock lock (createStreamLock); | |||
| if (! hasBeenCancelled) | |||
| stream = GlobalRef (LocalRef<jobject> (env->CallStaticObjectMethod (HTTPStream, | |||
| HTTPStream.createHTTPStream, | |||
| javaString (address).get(), | |||
| (jboolean) addParametersToRequestBody, | |||
| postDataArray, | |||
| javaString (headers).get(), | |||
| (jint) timeOutMs, | |||
| statusCodeArray, | |||
| responseHeaderBuffer.get(), | |||
| (jint) numRedirectsToFollow, | |||
| javaString (httpRequest).get()))); | |||
| } | |||
| if (stream != nullptr && ! stream.callBooleanMethod (HTTPStream.connect)) | |||
| stream.clear(); | |||
| jint* const statusCodeElements = env->GetIntArrayElements (statusCodeArray, nullptr); | |||
| statusCode = statusCodeElements[0]; | |||
| env->ReleaseIntArrayElements (statusCodeArray, statusCodeElements, 0); | |||
| env->DeleteLocalRef (statusCodeArray); | |||
| if (postDataArray != nullptr) | |||
| env->DeleteLocalRef (postDataArray); | |||
| if (stream != nullptr) | |||
| { | |||
| StringArray headerLines; | |||
| { | |||
| LocalRef<jstring> headersString ((jstring) env->CallObjectMethod (responseHeaderBuffer.get(), | |||
| StringBuffer.toString)); | |||
| headerLines.addLines (juceString (env, headersString)); | |||
| } | |||
| for (int i = 0; i < headerLines.size(); ++i) | |||
| { | |||
| const String& header = headerLines[i]; | |||
| const String key (header.upToFirstOccurrenceOf (": ", false, false)); | |||
| const String value (header.fromFirstOccurrenceOf (": ", false, false)); | |||
| const String previousValue (responseHeaders[key]); | |||
| responseHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| // WebInputStream methods | |||
| void withExtraHeaders (const String& extraHeaders) | |||
| { | |||
| if (! headers.endsWithChar ('\n') && headers.isNotEmpty()) | |||
| headers << "\r\n"; | |||
| headers << extraHeaders; | |||
| if (! headers.endsWithChar ('\n') && headers.isNotEmpty()) | |||
| headers << "\r\n"; | |||
| } | |||
| void withCustomRequestCommand (const String& customRequestCommand) { httpRequest = customRequestCommand; } | |||
| void withConnectionTimeout (int timeoutInMs) { timeOutMs = timeoutInMs; } | |||
| void withNumRedirectsToFollow (int maxRedirectsToFollow) { numRedirectsToFollow = maxRedirectsToFollow; } | |||
| StringPairArray getRequestHeaders() const { return WebInputStream::parseHttpHeaders (headers); } | |||
| StringPairArray getResponseHeaders() const { return responseHeaders; } | |||
| int getStatusCode() const { return statusCode; } | |||
| //============================================================================== | |||
| bool isError() const { return stream == nullptr; } | |||
| bool isExhausted() { return (isContentURL ? eofStreamReached : stream != nullptr && stream.callBooleanMethod (HTTPStream.isExhausted)); } | |||
| int64 getTotalLength() { return (isContentURL ? -1 : (stream != nullptr ? stream.callLongMethod (HTTPStream.getTotalLength) : 0)); } | |||
| int64 getPosition() { return (isContentURL ? readPosition : (stream != nullptr ? stream.callLongMethod (HTTPStream.getPosition) : 0)); } | |||
| //============================================================================== | |||
| bool setPosition (int64 wantedPos) | |||
| { | |||
| if (isContentURL) | |||
| { | |||
| if (wantedPos < readPosition) | |||
| return false; | |||
| auto bytesToSkip = wantedPos - readPosition; | |||
| if (bytesToSkip == 0) | |||
| return true; | |||
| HeapBlock<char> buffer (bytesToSkip); | |||
| return (read (buffer.getData(), (int) bytesToSkip) > 0); | |||
| } | |||
| return stream != nullptr && stream.callBooleanMethod (HTTPStream.setPosition, (jlong) wantedPos); | |||
| } | |||
| int read (void* buffer, int bytesToRead) | |||
| { | |||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||
| const ScopedLock lock (createStreamLock); | |||
| if (stream == nullptr) | |||
| return 0; | |||
| JNIEnv* env = getEnv(); | |||
| jbyteArray javaArray = env->NewByteArray (bytesToRead); | |||
| auto numBytes = (isContentURL ? stream.callIntMethod (AndroidInputStream.read, javaArray, 0, (jint) bytesToRead) | |||
| : stream.callIntMethod (HTTPStream.read, javaArray, (jint) bytesToRead)); | |||
| if (numBytes > 0) | |||
| env->GetByteArrayRegion (javaArray, 0, numBytes, static_cast<jbyte*> (buffer)); | |||
| env->DeleteLocalRef (javaArray); | |||
| readPosition += jmax (0, numBytes); | |||
| if (numBytes == -1) | |||
| eofStreamReached = true; | |||
| return numBytes; | |||
| } | |||
| //============================================================================== | |||
| int statusCode = 0; | |||
| private: | |||
| const URL url; | |||
| const bool isContentURL, addParametersToRequestBody, hasBodyDataToSend; | |||
| bool eofStreamReached = false; | |||
| int numRedirectsToFollow = 5, timeOutMs = 0; | |||
| String httpRequest, headers; | |||
| StringPairArray responseHeaders; | |||
| CriticalSection createStreamLock; | |||
| bool hasBeenCancelled = false; | |||
| int readPosition = 0; | |||
| GlobalRef stream; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||
| }; | |||
| std::unique_ptr<URL::DownloadTask> URL::downloadToFile (const File& targetLocation, const DownloadTaskOptions& options) | |||
| { | |||
| return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, options); | |||
| } | |||
| //============================================================================== | |||
| #if __ANDROID_API__ < 24 // Android support for getifadds was added in Android 7.0 (API 24) so the posix implementation does not apply | |||
| static IPAddress makeAddress (const sockaddr_in *addr_in) | |||
| { | |||
| if (addr_in->sin_addr.s_addr == INADDR_NONE) | |||
| return {}; | |||
| return IPAddress (ntohl (addr_in->sin_addr.s_addr)); | |||
| } | |||
| struct InterfaceInfo | |||
| { | |||
| IPAddress interfaceAddress, broadcastAddress; | |||
| }; | |||
| static Array<InterfaceInfo> findIPAddresses (int dummySocket) | |||
| { | |||
| ifconf cfg; | |||
| HeapBlock<char> buffer; | |||
| int bufferSize = 1024; | |||
| do | |||
| { | |||
| bufferSize *= 2; | |||
| buffer.calloc (bufferSize); | |||
| cfg.ifc_len = bufferSize; | |||
| cfg.ifc_buf = buffer; | |||
| if (ioctl (dummySocket, SIOCGIFCONF, &cfg) < 0 && errno != EINVAL) | |||
| return {}; | |||
| } while (bufferSize < cfg.ifc_len + 2 * (int) (IFNAMSIZ + sizeof (struct sockaddr_in6))); | |||
| Array<InterfaceInfo> result; | |||
| for (size_t i = 0; i < (size_t) cfg.ifc_len / (size_t) sizeof (struct ifreq); ++i) | |||
| { | |||
| auto& item = cfg.ifc_req[i]; | |||
| if (item.ifr_addr.sa_family == AF_INET) | |||
| { | |||
| InterfaceInfo info; | |||
| info.interfaceAddress = makeAddress (reinterpret_cast<const sockaddr_in*> (&item.ifr_addr)); | |||
| if (! info.interfaceAddress.isNull()) | |||
| { | |||
| if (ioctl (dummySocket, SIOCGIFBRDADDR, &item) == 0) | |||
| info.broadcastAddress = makeAddress (reinterpret_cast<const sockaddr_in*> (&item.ifr_broadaddr)); | |||
| result.add (info); | |||
| } | |||
| } | |||
| else if (item.ifr_addr.sa_family == AF_INET6) | |||
| { | |||
| // TODO: IPv6 | |||
| } | |||
| } | |||
| return result; | |||
| } | |||
| static Array<InterfaceInfo> findIPAddresses() | |||
| { | |||
| auto dummySocket = socket (AF_INET, SOCK_DGRAM, 0); // a dummy socket to execute the IO control | |||
| if (dummySocket < 0) | |||
| return {}; | |||
| auto result = findIPAddresses (dummySocket); | |||
| ::close (dummySocket); | |||
| return result; | |||
| } | |||
| void IPAddress::findAllAddresses (Array<IPAddress>& result, bool /*includeIPv6*/) | |||
| { | |||
| for (auto& a : findIPAddresses()) | |||
| result.add (a.interfaceAddress); | |||
| } | |||
| IPAddress IPAddress::getInterfaceBroadcastAddress (const IPAddress& address) | |||
| { | |||
| for (auto& a : findIPAddresses()) | |||
| if (a.interfaceAddress == address) | |||
| return a.broadcastAddress; | |||
| return {}; | |||
| } | |||
| #endif | |||
| } // namespace juce | |||
| @@ -1,261 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| static String jucePermissionToAndroidPermission (RuntimePermissions::PermissionID permission) | |||
| { | |||
| switch (permission) | |||
| { | |||
| case RuntimePermissions::recordAudio: return "android.permission.RECORD_AUDIO"; | |||
| case RuntimePermissions::bluetoothMidi: return "android.permission.ACCESS_FINE_LOCATION"; | |||
| case RuntimePermissions::readExternalStorage: return "android.permission.READ_EXTERNAL_STORAGE"; | |||
| case RuntimePermissions::writeExternalStorage: return "android.permission.WRITE_EXTERNAL_STORAGE"; | |||
| case RuntimePermissions::camera: return "android.permission.CAMERA"; | |||
| } | |||
| // invalid permission | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| static RuntimePermissions::PermissionID androidPermissionToJucePermission (const String& permission) | |||
| { | |||
| if (permission == "android.permission.RECORD_AUDIO") return RuntimePermissions::recordAudio; | |||
| else if (permission == "android.permission.ACCESS_FINE_LOCATION") return RuntimePermissions::bluetoothMidi; | |||
| else if (permission == "android.permission.READ_EXTERNAL_STORAGE") return RuntimePermissions::readExternalStorage; | |||
| else if (permission == "android.permission.WRITE_EXTERNAL_STORAGE") return RuntimePermissions::writeExternalStorage; | |||
| else if (permission == "android.permission.CAMERA") return RuntimePermissions::camera; | |||
| return static_cast<RuntimePermissions::PermissionID> (-1); | |||
| } | |||
| //============================================================================== | |||
| struct PermissionsRequest | |||
| { | |||
| PermissionsRequest() {} | |||
| // using "= default" on the following method triggers an internal compiler error | |||
| // in Android NDK 17 | |||
| PermissionsRequest (const PermissionsRequest& o) | |||
| : callback (o.callback), permission (o.permission) | |||
| {} | |||
| PermissionsRequest (PermissionsRequest&& o) | |||
| : callback (std::move (o.callback)), permission (o.permission) | |||
| { | |||
| o.permission = static_cast<RuntimePermissions::PermissionID> (-1); | |||
| } | |||
| PermissionsRequest (RuntimePermissions::Callback && callbackToUse, | |||
| RuntimePermissions::PermissionID permissionToRequest) | |||
| : callback (std::move (callbackToUse)), permission (permissionToRequest) | |||
| {} | |||
| PermissionsRequest& operator= (const PermissionsRequest & o) | |||
| { | |||
| callback = o.callback; | |||
| permission = o.permission; | |||
| return *this; | |||
| } | |||
| PermissionsRequest& operator= (PermissionsRequest && o) | |||
| { | |||
| callback = std::move (o.callback); | |||
| permission = o.permission; | |||
| return *this; | |||
| } | |||
| RuntimePermissions::Callback callback; | |||
| RuntimePermissions::PermissionID permission; | |||
| }; | |||
| //============================================================================== | |||
| struct PermissionsOverlay : FragmentOverlay | |||
| { | |||
| PermissionsOverlay (CriticalSection& cs) : overlayGuard (cs) {} | |||
| ~PermissionsOverlay() override = default; | |||
| struct PermissionResult | |||
| { | |||
| PermissionsRequest request; | |||
| bool granted; | |||
| }; | |||
| void onStart() override { onRequestPermissionsResult (0, {}, {}); } | |||
| void onRequestPermissionsResult (int /*requestCode*/, | |||
| const StringArray& permissions, | |||
| const Array<int>& grantResults) override | |||
| { | |||
| std::vector<PermissionResult> results; | |||
| { | |||
| ScopedLock lock (overlayGuard); | |||
| for (auto it = requests.begin(); it != requests.end();) | |||
| { | |||
| auto& request = *it; | |||
| if (RuntimePermissions::isGranted (request.permission)) | |||
| { | |||
| results.push_back ({std::move (request), true}); | |||
| it = requests.erase (it); | |||
| } | |||
| else | |||
| { | |||
| ++it; | |||
| } | |||
| } | |||
| auto n = permissions.size(); | |||
| for (int i = 0; i < n; ++i) | |||
| { | |||
| auto permission = androidPermissionToJucePermission (permissions[i]); | |||
| auto granted = (grantResults.getReference (i) == 0); | |||
| for (auto it = requests.begin(); it != requests.end();) | |||
| { | |||
| auto& request = *it; | |||
| if (request.permission == permission) | |||
| { | |||
| results.push_back ({std::move (request), granted}); | |||
| it = requests.erase (it); | |||
| } | |||
| else | |||
| { | |||
| ++it; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| for (const auto& result : results) | |||
| if (result.request.callback) | |||
| result.request.callback (result.granted); | |||
| { | |||
| auto* env = getEnv(); | |||
| ScopedLock lock (overlayGuard); | |||
| if (requests.size() > 0) | |||
| { | |||
| auto &request = requests.front(); | |||
| StringArray permissionsArray{ | |||
| jucePermissionToAndroidPermission (request.permission)}; | |||
| auto jPermissionsArray = juceStringArrayToJava (permissionsArray); | |||
| auto requestPermissionsMethodID | |||
| = env->GetMethodID(AndroidFragment, "requestPermissions", "([Ljava/lang/String;I)V"); | |||
| // this code should only be reached for SDKs >= 23, so this method should be | |||
| // be available | |||
| jassert(requestPermissionsMethodID != nullptr); | |||
| env->CallVoidMethod (getNativeHandle(), requestPermissionsMethodID, jPermissionsArray.get (), 0); | |||
| } | |||
| else | |||
| { | |||
| getSingleton() = nullptr; | |||
| } | |||
| } | |||
| } | |||
| static std::unique_ptr<PermissionsOverlay>& getSingleton() | |||
| { | |||
| static std::unique_ptr<PermissionsOverlay> instance; | |||
| return instance; | |||
| } | |||
| CriticalSection& overlayGuard; | |||
| std::vector<PermissionsRequest> requests; | |||
| }; | |||
| //============================================================================== | |||
| void RuntimePermissions::request (PermissionID permission, Callback callback) | |||
| { | |||
| auto requestedPermission = jucePermissionToAndroidPermission (permission); | |||
| if (! isPermissionDeclaredInManifest (requestedPermission)) | |||
| { | |||
| // Error! If you want to be able to request this runtime permission, you | |||
| // also need to declare it in your app's manifest. You can do so via | |||
| // the Projucer. Otherwise this can't work. | |||
| jassertfalse; | |||
| callback (false); | |||
| return; | |||
| } | |||
| auto alreadyGranted = isGranted (permission); | |||
| if (alreadyGranted || getAndroidSDKVersion() < 23) | |||
| { | |||
| callback (alreadyGranted); | |||
| return; | |||
| } | |||
| PermissionsRequest request (std::move (callback), permission); | |||
| static CriticalSection overlayGuard; | |||
| ScopedLock lock (overlayGuard); | |||
| std::unique_ptr<PermissionsOverlay>& overlay = PermissionsOverlay::getSingleton(); | |||
| bool alreadyOpen = true; | |||
| if (overlay == nullptr) | |||
| { | |||
| overlay.reset (new PermissionsOverlay (overlayGuard)); | |||
| alreadyOpen = false; | |||
| } | |||
| overlay->requests.push_back (std::move (request)); | |||
| if (! alreadyOpen) | |||
| overlay->open(); | |||
| } | |||
| bool RuntimePermissions::isRequired (PermissionID /*permission*/) | |||
| { | |||
| return getAndroidSDKVersion() >= 23; | |||
| } | |||
| bool RuntimePermissions::isGranted (PermissionID permission) | |||
| { | |||
| auto* env = getEnv(); | |||
| auto requestedPermission = jucePermissionToAndroidPermission (permission); | |||
| int result = env->CallIntMethod (getAppContext().get(), AndroidContext.checkCallingOrSelfPermission, | |||
| javaString (requestedPermission).get()); | |||
| return result == 0 /* PERMISSION_GRANTED */; | |||
| } | |||
| } // namespace juce | |||
| @@ -1,241 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| namespace AndroidStatsHelpers | |||
| { | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (getProperty, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;") | |||
| DECLARE_JNI_CLASS (SystemClass, "java/lang/System") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (getDefault, "getDefault", "()Ljava/util/Locale;") \ | |||
| METHOD (getCountry, "getCountry", "()Ljava/lang/String;") \ | |||
| METHOD (getLanguage, "getLanguage", "()Ljava/lang/String;") | |||
| DECLARE_JNI_CLASS (JavaLocale, "java/util/Locale") | |||
| #undef JNI_CLASS_MEMBERS | |||
| static String getSystemProperty (const String& name) | |||
| { | |||
| return juceString (LocalRef<jstring> ((jstring) getEnv()->CallStaticObjectMethod (SystemClass, | |||
| SystemClass.getProperty, | |||
| javaString (name).get()))); | |||
| } | |||
| static String getLocaleValue (bool isRegion) | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> locale (env->CallStaticObjectMethod (JavaLocale, JavaLocale.getDefault)); | |||
| auto stringResult = isRegion ? env->CallObjectMethod (locale.get(), JavaLocale.getCountry) | |||
| : env->CallObjectMethod (locale.get(), JavaLocale.getLanguage); | |||
| return juceString (LocalRef<jstring> ((jstring) stringResult)); | |||
| } | |||
| static String getAndroidOsBuildValue (const char* fieldName) | |||
| { | |||
| return juceString (LocalRef<jstring> ((jstring) getEnv()->GetStaticObjectField ( | |||
| AndroidBuild, getEnv()->GetStaticFieldID (AndroidBuild, fieldName, "Ljava/lang/String;")))); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() | |||
| { | |||
| return Android; | |||
| } | |||
| String SystemStats::getOperatingSystemName() | |||
| { | |||
| return "Android " + AndroidStatsHelpers::getSystemProperty ("os.version"); | |||
| } | |||
| String SystemStats::getDeviceDescription() | |||
| { | |||
| return AndroidStatsHelpers::getAndroidOsBuildValue ("MODEL") | |||
| + "-" + AndroidStatsHelpers::getAndroidOsBuildValue ("SERIAL"); | |||
| } | |||
| String SystemStats::getDeviceManufacturer() | |||
| { | |||
| return AndroidStatsHelpers::getAndroidOsBuildValue ("MANUFACTURER"); | |||
| } | |||
| bool SystemStats::isOperatingSystem64Bit() | |||
| { | |||
| #if JUCE_64BIT | |||
| return true; | |||
| #else | |||
| return false; | |||
| #endif | |||
| } | |||
| String SystemStats::getCpuVendor() | |||
| { | |||
| return AndroidStatsHelpers::getSystemProperty ("os.arch"); | |||
| } | |||
| String SystemStats::getCpuModel() | |||
| { | |||
| return readPosixConfigFileValue ("/proc/cpuinfo", "Hardware"); | |||
| } | |||
| int SystemStats::getCpuSpeedInMegahertz() | |||
| { | |||
| int maxFreqKHz = 0; | |||
| for (int i = 0; i < getNumCpus(); ++i) | |||
| { | |||
| int freqKHz = File ("/sys/devices/system/cpu/cpu" + String(i) + "/cpufreq/cpuinfo_max_freq") | |||
| .loadFileAsString() | |||
| .getIntValue(); | |||
| maxFreqKHz = jmax (freqKHz, maxFreqKHz); | |||
| } | |||
| return maxFreqKHz / 1000; | |||
| } | |||
| int SystemStats::getMemorySizeInMegabytes() | |||
| { | |||
| #if __ANDROID_API__ >= 9 | |||
| struct sysinfo sysi; | |||
| if (sysinfo (&sysi) == 0) | |||
| return static_cast<int> ((sysi.totalram * sysi.mem_unit) / (1024 * 1024)); | |||
| #endif | |||
| return 0; | |||
| } | |||
| int SystemStats::getPageSize() | |||
| { | |||
| return static_cast<int> (sysconf (_SC_PAGESIZE)); | |||
| } | |||
| //============================================================================== | |||
| String SystemStats::getLogonName() | |||
| { | |||
| if (const char* user = getenv ("USER")) | |||
| return CharPointer_UTF8 (user); | |||
| if (struct passwd* const pw = getpwuid (getuid())) | |||
| return CharPointer_UTF8 (pw->pw_name); | |||
| return {}; | |||
| } | |||
| String SystemStats::getFullUserName() | |||
| { | |||
| return getLogonName(); | |||
| } | |||
| String SystemStats::getComputerName() | |||
| { | |||
| char name [256] = { 0 }; | |||
| if (gethostname (name, sizeof (name) - 1) == 0) | |||
| return name; | |||
| return {}; | |||
| } | |||
| String SystemStats::getUserLanguage() { return AndroidStatsHelpers::getLocaleValue (false); } | |||
| String SystemStats::getUserRegion() { return AndroidStatsHelpers::getLocaleValue (true); } | |||
| String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); } | |||
| //============================================================================== | |||
| void CPUInformation::initialise() noexcept | |||
| { | |||
| numPhysicalCPUs = numLogicalCPUs = jmax ((int) 1, (int) android_getCpuCount()); | |||
| auto cpuFamily = android_getCpuFamily(); | |||
| auto cpuFeatures = android_getCpuFeatures(); | |||
| if (cpuFamily == ANDROID_CPU_FAMILY_X86 || cpuFamily == ANDROID_CPU_FAMILY_X86_64) | |||
| { | |||
| hasMMX = hasSSE = hasSSE2 = (cpuFamily == ANDROID_CPU_FAMILY_X86_64); | |||
| hasSSSE3 = ((cpuFeatures & ANDROID_CPU_X86_FEATURE_SSSE3) != 0); | |||
| hasSSE41 = ((cpuFeatures & ANDROID_CPU_X86_FEATURE_SSE4_1) != 0); | |||
| hasSSE42 = ((cpuFeatures & ANDROID_CPU_X86_FEATURE_SSE4_2) != 0); | |||
| hasAVX = ((cpuFeatures & ANDROID_CPU_X86_FEATURE_AVX) != 0); | |||
| hasAVX2 = ((cpuFeatures & ANDROID_CPU_X86_FEATURE_AVX2) != 0); | |||
| // Google does not distinguish between MMX, SSE, SSE2, SSE3 and SSSE3. So | |||
| // I assume (and quick Google searches seem to confirm this) that there are | |||
| // only devices out there that either support all of this or none of this. | |||
| if (hasSSSE3) | |||
| hasMMX = hasSSE = hasSSE2 = hasSSE3 = true; | |||
| } | |||
| else if (cpuFamily == ANDROID_CPU_FAMILY_ARM) | |||
| { | |||
| hasNeon = ((cpuFeatures & ANDROID_CPU_ARM_FEATURE_NEON) != 0); | |||
| } | |||
| else if (cpuFamily == ANDROID_CPU_FAMILY_ARM64) | |||
| { | |||
| // all arm 64-bit cpus have neon | |||
| hasNeon = true; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| uint32 juce_millisecondsSinceStartup() noexcept | |||
| { | |||
| timespec t; | |||
| clock_gettime (CLOCK_MONOTONIC, &t); | |||
| return static_cast<uint32> (t.tv_sec) * 1000U + static_cast<uint32> (t.tv_nsec) / 1000000U; | |||
| } | |||
| int64 Time::getHighResolutionTicks() noexcept | |||
| { | |||
| timespec t; | |||
| clock_gettime (CLOCK_MONOTONIC, &t); | |||
| return (t.tv_sec * (int64) 1000000) + (t.tv_nsec / 1000); | |||
| } | |||
| int64 Time::getHighResolutionTicksPerSecond() noexcept | |||
| { | |||
| return 1000000; // (microseconds) | |||
| } | |||
| double Time::getMillisecondCounterHiRes() noexcept | |||
| { | |||
| return (double) getHighResolutionTicks() * 0.001; | |||
| } | |||
| bool Time::setSystemTimeToThisTime() const | |||
| { | |||
| jassertfalse; | |||
| return false; | |||
| } | |||
| } // namespace juce | |||
| @@ -1,395 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| /* | |||
| Note that a lot of methods that you'd expect to find in this file actually | |||
| live in juce_posix_SharedCode.h! | |||
| */ | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| FIELD (activityInfo, "activityInfo", "Landroid/content/pm/ActivityInfo;") | |||
| DECLARE_JNI_CLASS (AndroidResolveInfo, "android/content/pm/ResolveInfo") | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| JavaVM* androidJNIJavaVM = nullptr; | |||
| jobject androidApkContext = nullptr; | |||
| //============================================================================== | |||
| JNIEnv* getEnv() noexcept | |||
| { | |||
| if (androidJNIJavaVM != nullptr) | |||
| { | |||
| JNIEnv* env; | |||
| androidJNIJavaVM->AttachCurrentThread (&env, nullptr); | |||
| return env; | |||
| } | |||
| // You did not call Thread::initialiseJUCE which must be called at least once in your apk | |||
| // before using any JUCE APIs. The Projucer will automatically generate java code | |||
| // which will invoke Thread::initialiseJUCE for you. | |||
| jassertfalse; | |||
| return nullptr; | |||
| } | |||
| static void JNICALL juce_JavainitialiseJUCE (JNIEnv* env, jobject /*jclass*/, jobject context) | |||
| { | |||
| Thread::initialiseJUCE (env, context); | |||
| } | |||
| extern "C" jint JNIEXPORT JNI_OnLoad (JavaVM* vm, void*) | |||
| { | |||
| // Huh? JNI_OnLoad was called two times! | |||
| jassert (androidJNIJavaVM == nullptr); | |||
| androidJNIJavaVM = vm; | |||
| auto* env = getEnv(); | |||
| // register the initialisation function | |||
| auto juceJavaClass = env->FindClass("com/rmsl/juce/Java"); | |||
| if (juceJavaClass != nullptr) | |||
| { | |||
| JNINativeMethod method {"initialiseJUCE", "(Landroid/content/Context;)V", | |||
| reinterpret_cast<void*> (juce_JavainitialiseJUCE)}; | |||
| auto status = env->RegisterNatives (juceJavaClass, &method, 1); | |||
| jassert (status == 0); | |||
| } | |||
| else | |||
| { | |||
| // com.rmsl.juce.Java class not found. Apparently this project is a library | |||
| // or was not generated by the Projucer. That's ok, the user will have to | |||
| // call Thread::initialiseJUCE manually | |||
| env->ExceptionClear(); | |||
| } | |||
| JNIClassBase::initialiseAllClasses (env); | |||
| return JNI_VERSION_1_2; | |||
| } | |||
| //============================================================================== | |||
| class JuceActivityWatcher : public ActivityLifecycleCallbacks | |||
| { | |||
| public: | |||
| JuceActivityWatcher() | |||
| { | |||
| LocalRef<jobject> appContext (getAppContext()); | |||
| if (appContext != nullptr) | |||
| { | |||
| auto* env = getEnv(); | |||
| myself = GlobalRef (CreateJavaInterface (this, "android/app/Application$ActivityLifecycleCallbacks")); | |||
| env->CallVoidMethod (appContext.get(), AndroidApplication.registerActivityLifecycleCallbacks, myself.get()); | |||
| } | |||
| checkActivityIsMain (androidApkContext); | |||
| } | |||
| ~JuceActivityWatcher() override | |||
| { | |||
| LocalRef<jobject> appContext (getAppContext()); | |||
| if (appContext != nullptr && myself != nullptr) | |||
| { | |||
| auto* env = getEnv(); | |||
| env->CallVoidMethod (appContext.get(), AndroidApplication.unregisterActivityLifecycleCallbacks, myself.get()); | |||
| clear(); | |||
| myself.clear(); | |||
| } | |||
| } | |||
| void onActivityStarted (jobject activity) override | |||
| { | |||
| auto* env = getEnv(); | |||
| checkActivityIsMain (activity); | |||
| ScopedLock lock (currentActivityLock); | |||
| if (currentActivity != nullptr) | |||
| { | |||
| // see Clarification June 2001 in JNI reference for why this is | |||
| // necessary | |||
| LocalRef<jobject> localStorage (env->NewLocalRef (currentActivity)); | |||
| if (env->IsSameObject (localStorage.get(), activity) != 0) | |||
| return; | |||
| env->DeleteWeakGlobalRef (currentActivity); | |||
| currentActivity = nullptr; | |||
| } | |||
| if (activity != nullptr) | |||
| currentActivity = env->NewWeakGlobalRef (activity); | |||
| } | |||
| void onActivityStopped (jobject activity) override | |||
| { | |||
| auto* env = getEnv(); | |||
| ScopedLock lock (currentActivityLock); | |||
| if (currentActivity != nullptr) | |||
| { | |||
| // important that the comparison happens in this order | |||
| // to avoid race condition where the weak reference becomes null | |||
| // just after the first check | |||
| if (env->IsSameObject (currentActivity, activity) != 0 | |||
| || env->IsSameObject (currentActivity, nullptr) != 0) | |||
| { | |||
| env->DeleteWeakGlobalRef (currentActivity); | |||
| currentActivity = nullptr; | |||
| } | |||
| } | |||
| } | |||
| LocalRef<jobject> getCurrent() | |||
| { | |||
| ScopedLock lock (currentActivityLock); | |||
| return LocalRef<jobject> (getEnv()->NewLocalRef (currentActivity)); | |||
| } | |||
| LocalRef<jobject> getMain() | |||
| { | |||
| ScopedLock lock (currentActivityLock); | |||
| return LocalRef<jobject> (getEnv()->NewLocalRef (mainActivity)); | |||
| } | |||
| static JuceActivityWatcher& getInstance() | |||
| { | |||
| static JuceActivityWatcher activityWatcher; | |||
| return activityWatcher; | |||
| } | |||
| private: | |||
| void checkActivityIsMain (jobject context) | |||
| { | |||
| auto* env = getEnv(); | |||
| ScopedLock lock (currentActivityLock); | |||
| if (mainActivity != nullptr) | |||
| { | |||
| if (env->IsSameObject (mainActivity, nullptr) != 0) | |||
| { | |||
| env->DeleteWeakGlobalRef (mainActivity); | |||
| mainActivity = nullptr; | |||
| } | |||
| } | |||
| if (mainActivity == nullptr) | |||
| { | |||
| LocalRef<jobject> appContext (getAppContext()); | |||
| auto mainActivityPath = getMainActivityClassPath(); | |||
| if (mainActivityPath.isNotEmpty()) | |||
| { | |||
| auto clasz = env->GetObjectClass (context); | |||
| auto activityPath = juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (clasz, JavaClass.getName))); | |||
| // This may be problematic for apps which use several activities with the same type. We just | |||
| // assume that the very first activity of this type is the main one | |||
| if (activityPath == mainActivityPath) | |||
| mainActivity = env->NewWeakGlobalRef (context); | |||
| } | |||
| } | |||
| } | |||
| static String getMainActivityClassPath() | |||
| { | |||
| static String mainActivityClassPath; | |||
| if (mainActivityClassPath.isEmpty()) | |||
| { | |||
| LocalRef<jobject> appContext (getAppContext()); | |||
| if (appContext != nullptr) | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> pkgManager (env->CallObjectMethod (appContext.get(), AndroidContext.getPackageManager)); | |||
| LocalRef<jstring> pkgName ((jstring) env->CallObjectMethod (appContext.get(), AndroidContext.getPackageName)); | |||
| LocalRef<jobject> intent (env->NewObject (AndroidIntent, AndroidIntent.constructWithString, | |||
| javaString ("android.intent.action.MAIN").get())); | |||
| intent = LocalRef<jobject> (env->CallObjectMethod (intent.get(), | |||
| AndroidIntent.setPackage, | |||
| pkgName.get())); | |||
| LocalRef<jobject> resolveInfo (env->CallObjectMethod (pkgManager.get(), AndroidPackageManager.resolveActivity, intent.get(), 0)); | |||
| if (resolveInfo != nullptr) | |||
| { | |||
| LocalRef<jobject> activityInfo (env->GetObjectField (resolveInfo.get(), AndroidResolveInfo.activityInfo)); | |||
| LocalRef<jstring> jName ((jstring) env->GetObjectField (activityInfo.get(), AndroidPackageItemInfo.name)); | |||
| LocalRef<jstring> jPackage ((jstring) env->GetObjectField (activityInfo.get(), AndroidPackageItemInfo.packageName)); | |||
| mainActivityClassPath = juceString (jName); | |||
| } | |||
| } | |||
| } | |||
| return mainActivityClassPath; | |||
| } | |||
| GlobalRef myself; | |||
| CriticalSection currentActivityLock; | |||
| jweak currentActivity = nullptr; | |||
| jweak mainActivity = nullptr; | |||
| }; | |||
| //============================================================================== | |||
| #if JUCE_MODULE_AVAILABLE_juce_events && JUCE_ANDROID | |||
| void juce_juceEventsAndroidStartApp(); | |||
| #endif | |||
| void Thread::initialiseJUCE (void* jniEnv, void* context) | |||
| { | |||
| static CriticalSection cs; | |||
| ScopedLock lock (cs); | |||
| // jniEnv and context should not be null! | |||
| jassert (jniEnv != nullptr && context != nullptr); | |||
| auto* env = static_cast<JNIEnv*> (jniEnv); | |||
| if (androidJNIJavaVM == nullptr) | |||
| { | |||
| JavaVM* javaVM = nullptr; | |||
| auto status = env->GetJavaVM (&javaVM); | |||
| jassert (status == 0 && javaVM != nullptr); | |||
| androidJNIJavaVM = javaVM; | |||
| } | |||
| static bool firstCall = true; | |||
| if (firstCall) | |||
| { | |||
| firstCall = false; | |||
| // if we ever support unloading then this should probably be a weak reference | |||
| androidApkContext = env->NewGlobalRef (static_cast<jobject> (context)); | |||
| JuceActivityWatcher::getInstance(); | |||
| #if JUCE_MODULE_AVAILABLE_juce_events && JUCE_ANDROID | |||
| juce_juceEventsAndroidStartApp(); | |||
| #endif | |||
| } | |||
| } | |||
| //============================================================================== | |||
| LocalRef<jobject> getAppContext() noexcept | |||
| { | |||
| auto* env = getEnv(); | |||
| auto context = androidApkContext; | |||
| // You did not call Thread::initialiseJUCE which must be called at least once in your apk | |||
| // before using any JUCE APIs. The Projucer will automatically generate java code | |||
| // which will invoke Thread::initialiseJUCE for you. | |||
| jassert (env != nullptr && context != nullptr); | |||
| if (context == nullptr) | |||
| return LocalRef<jobject>(); | |||
| if (env->IsInstanceOf (context, AndroidApplication) != 0) | |||
| return LocalRef<jobject> (env->NewLocalRef (context)); | |||
| LocalRef<jobject> applicationContext (env->CallObjectMethod (context, AndroidContext.getApplicationContext)); | |||
| if (applicationContext == nullptr) | |||
| return LocalRef<jobject> (env->NewLocalRef (context)); | |||
| return applicationContext; | |||
| } | |||
| LocalRef<jobject> getCurrentActivity() noexcept | |||
| { | |||
| return JuceActivityWatcher::getInstance().getCurrent(); | |||
| } | |||
| LocalRef<jobject> getMainActivity() noexcept | |||
| { | |||
| return JuceActivityWatcher::getInstance().getMain(); | |||
| } | |||
| //============================================================================== | |||
| // sets the process to 0=low priority, 1=normal, 2=high, 3=realtime | |||
| JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior) | |||
| { | |||
| // TODO | |||
| struct sched_param param; | |||
| int policy, maxp, minp; | |||
| const int p = (int) prior; | |||
| if (p <= 1) | |||
| policy = SCHED_OTHER; | |||
| else | |||
| policy = SCHED_RR; | |||
| minp = sched_get_priority_min (policy); | |||
| maxp = sched_get_priority_max (policy); | |||
| if (p < 2) | |||
| param.sched_priority = 0; | |||
| else if (p == 2 ) | |||
| // Set to middle of lower realtime priority range | |||
| param.sched_priority = minp + (maxp - minp) / 4; | |||
| else | |||
| // Set to middle of higher realtime priority range | |||
| param.sched_priority = minp + (3 * (maxp - minp) / 4); | |||
| pthread_setschedparam (pthread_self(), policy, ¶m); | |||
| } | |||
| JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept | |||
| { | |||
| StringArray lines; | |||
| File ("/proc/self/status").readLines (lines); | |||
| for (int i = lines.size(); --i >= 0;) // (NB - it's important that this runs in reverse order) | |||
| if (lines[i].upToFirstOccurrenceOf (":", false, false).trim().equalsIgnoreCase ("TracerPid")) | |||
| return (lines[i].fromFirstOccurrenceOf (":", false, false).trim().getIntValue() > 0); | |||
| return false; | |||
| } | |||
| JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {} | |||
| JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} | |||
| } // namespace juce | |||
| @@ -502,7 +502,7 @@ public: | |||
| bool operator!= (const void* ptr) const { return ((const void*) block != ptr); } | |||
| ~ObjCBlock() { if (block != nullptr) [block release]; } | |||
| operator BlockType() { return block; } | |||
| operator BlockType() const { return block; } | |||
| private: | |||
| BlockType block; | |||
| @@ -288,6 +288,12 @@ bool File::hasWriteAccess() const | |||
| return false; | |||
| } | |||
| bool File::hasReadAccess() const | |||
| { | |||
| return fullPath.isNotEmpty() | |||
| && access (fullPath.toUTF8(), R_OK) == 0; | |||
| } | |||
| static bool setFileModeFlags (const String& fullPath, mode_t flags, bool shouldSet) noexcept | |||
| { | |||
| juce_statStruct info; | |||
| @@ -23,7 +23,7 @@ | |||
| namespace juce | |||
| { | |||
| #if (! defined (_MSC_VER) && ! defined (__uuidof)) | |||
| #if (JUCE_MINGW && JUCE_32BIT) || (! defined (_MSC_VER) && ! defined (__uuidof)) | |||
| #ifdef __uuidof | |||
| #undef __uuidof | |||
| #endif | |||
| @@ -159,6 +159,83 @@ namespace WindowsFileHelpers | |||
| return Result::fail (String (messageBuffer)); | |||
| } | |||
| // The docs for the Windows security API aren't very clear. Some parts of the following | |||
| // function (the flags passed to GetNamedSecurityInfo, duplicating the primary access token) | |||
| // were guided by the example at https://blog.aaronballman.com/2011/08/how-to-check-access-rights/ | |||
| static bool hasFileAccess (const File& file, DWORD accessType) | |||
| { | |||
| const auto& path = file.getFullPathName(); | |||
| if (path.isEmpty()) | |||
| return false; | |||
| struct PsecurityDescriptorGuard | |||
| { | |||
| ~PsecurityDescriptorGuard() { if (psecurityDescriptor != nullptr) LocalFree (psecurityDescriptor); } | |||
| PSECURITY_DESCRIPTOR psecurityDescriptor = nullptr; | |||
| }; | |||
| PsecurityDescriptorGuard descriptorGuard; | |||
| if (GetNamedSecurityInfo (path.toWideCharPointer(), | |||
| SE_FILE_OBJECT, | |||
| OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, | |||
| nullptr, | |||
| nullptr, | |||
| nullptr, | |||
| nullptr, | |||
| &descriptorGuard.psecurityDescriptor) != ERROR_SUCCESS) | |||
| { | |||
| return false; | |||
| } | |||
| struct HandleGuard | |||
| { | |||
| ~HandleGuard() { if (handle != INVALID_HANDLE_VALUE) CloseHandle (handle); } | |||
| HANDLE handle = nullptr; | |||
| }; | |||
| HandleGuard primaryTokenGuard; | |||
| if (! OpenProcessToken (GetCurrentProcess(), | |||
| TOKEN_IMPERSONATE | TOKEN_DUPLICATE | TOKEN_QUERY | STANDARD_RIGHTS_READ, | |||
| &primaryTokenGuard.handle)) | |||
| { | |||
| return false; | |||
| } | |||
| HandleGuard duplicatedTokenGuard; | |||
| if (! DuplicateToken (primaryTokenGuard.handle, | |||
| SecurityImpersonation, | |||
| &duplicatedTokenGuard.handle)) | |||
| { | |||
| return false; | |||
| } | |||
| GENERIC_MAPPING mapping { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS }; | |||
| MapGenericMask (&accessType, &mapping); | |||
| DWORD allowed = 0; | |||
| BOOL granted = false; | |||
| PRIVILEGE_SET set; | |||
| DWORD setSize = sizeof (set); | |||
| if (! AccessCheck (descriptorGuard.psecurityDescriptor, | |||
| duplicatedTokenGuard.handle, | |||
| accessType, | |||
| &mapping, | |||
| &set, | |||
| &setSize, | |||
| &allowed, | |||
| &granted)) | |||
| { | |||
| return false; | |||
| } | |||
| return granted != FALSE; | |||
| } | |||
| } // namespace WindowsFileHelpers | |||
| //============================================================================== | |||
| @@ -199,17 +276,25 @@ bool File::isDirectory() const | |||
| bool File::hasWriteAccess() const | |||
| { | |||
| if (fullPath.isEmpty()) | |||
| return true; | |||
| if (exists()) | |||
| { | |||
| const auto attr = WindowsFileHelpers::getAtts (fullPath); | |||
| auto attr = WindowsFileHelpers::getAtts (fullPath); | |||
| return WindowsFileHelpers::hasFileAccess (*this, GENERIC_WRITE) | |||
| && (attr == INVALID_FILE_ATTRIBUTES | |||
| || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 | |||
| || (attr & FILE_ATTRIBUTE_READONLY) == 0); | |||
| } | |||
| if ((! isDirectory()) && fullPath.containsChar (getSeparatorChar())) | |||
| return getParentDirectory().hasWriteAccess(); | |||
| // NB: According to MS, the FILE_ATTRIBUTE_READONLY attribute doesn't work for | |||
| // folders, and can be incorrectly set for some special folders, so we'll just say | |||
| // that folders are always writable. | |||
| return attr == INVALID_FILE_ATTRIBUTES | |||
| || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 | |||
| || (attr & FILE_ATTRIBUTE_READONLY) == 0; | |||
| return false; | |||
| } | |||
| bool File::hasReadAccess() const | |||
| { | |||
| return WindowsFileHelpers::hasFileAccess (*this, GENERIC_READ); | |||
| } | |||
| bool File::setFileReadOnlyInternal (bool shouldBeReadOnly) const | |||
| @@ -792,6 +792,11 @@ std::unique_ptr<InputStream> URL::createInputStream (const InputStreamOptions& o | |||
| std::unique_ptr<OutputStream> URL::createOutputStream() const | |||
| { | |||
| #if JUCE_ANDROID | |||
| if (auto stream = AndroidDocument::fromDocument (*this).createOutputStream()) | |||
| return stream; | |||
| #endif | |||
| if (isLocalFile()) | |||
| { | |||
| #if JUCE_IOS | |||
| @@ -802,11 +807,7 @@ std::unique_ptr<OutputStream> URL::createOutputStream() const | |||
| #endif | |||
| } | |||
| #if JUCE_ANDROID | |||
| return std::unique_ptr<OutputStream> (juce_CreateContentURIOutputStream (*this)); | |||
| #else | |||
| return nullptr; | |||
| #endif | |||
| } | |||
| //============================================================================== | |||
| @@ -72,8 +72,8 @@ | |||
| // MSVC | |||
| #if JUCE_MSVC | |||
| #if _MSC_FULL_VER < 190024210 // VS2015 | |||
| #error "JUCE requires Visual Studio 2015 Update 3 or later" | |||
| #if _MSC_FULL_VER < 191025017 // VS2017 | |||
| #error "JUCE requires Visual Studio 2017 or later" | |||
| #endif | |||
| #ifndef JUCE_EXCEPTIONS_DISABLED | |||
| @@ -27,9 +27,9 @@ | |||
| See also SystemStats::getJUCEVersion() for a string version. | |||
| */ | |||
| #define JUCE_MAJOR_VERSION 6 | |||
| #define JUCE_MINOR_VERSION 1 | |||
| #define JUCE_BUILDNUMBER 6 | |||
| #define JUCE_MAJOR_VERSION 7 | |||
| #define JUCE_MINOR_VERSION 0 | |||
| #define JUCE_BUILDNUMBER 1 | |||
| /** Current JUCE version number. | |||
| @@ -41,6 +41,11 @@ | |||
| */ | |||
| #define JUCE_VERSION ((JUCE_MAJOR_VERSION << 16) + (JUCE_MINOR_VERSION << 8) + JUCE_BUILDNUMBER) | |||
| #if ! DOXYGEN | |||
| #define JUCE_VERSION_ID \ | |||
| volatile auto juceVersionId = "juce_version_" JUCE_STRINGIFY(JUCE_MAJOR_VERSION) "_" JUCE_STRINGIFY(JUCE_MINOR_VERSION) "_" JUCE_STRINGIFY(JUCE_BUILDNUMBER); \ | |||
| ignoreUnused (juceVersionId); | |||
| #endif | |||
| //============================================================================== | |||
| #include <algorithm> | |||
| @@ -81,7 +81,7 @@ String ChildProcess::readAllProcessOutput() | |||
| } | |||
| uint32 ChildProcess::getPID() const noexcept | |||
| int ChildProcess::getPID() const noexcept | |||
| { | |||
| return activeProcess != nullptr ? activeProcess->getPID() : 0; | |||
| } | |||
| @@ -101,7 +101,7 @@ public: | |||
| */ | |||
| bool kill(); | |||
| uint32 getPID() const noexcept; | |||
| int getPID() const noexcept; | |||
| private: | |||
| //============================================================================== | |||
| @@ -35,7 +35,7 @@ | |||
| ID: juce_data_structures | |||
| vendor: juce | |||
| version: 6.1.6 | |||
| version: 7.0.1 | |||
| name: JUCE data model helper classes | |||
| description: Classes for undo/redo management, and smart data structures. | |||
| website: http://www.juce.com/juce | |||
| @@ -61,6 +61,8 @@ struct ChildProcessPingThread : public Thread, | |||
| int timeoutMs; | |||
| using AsyncUpdater::cancelPendingUpdate; | |||
| private: | |||
| Atomic<int> countdown; | |||
| @@ -97,6 +99,7 @@ struct ChildProcessCoordinator::Connection : public InterprocessConnection, | |||
| ~Connection() override | |||
| { | |||
| cancelPendingUpdate(); | |||
| stopThread (10000); | |||
| } | |||
| @@ -206,6 +209,7 @@ struct ChildProcessWorker::Connection : public InterprocessConnection, | |||
| ~Connection() override | |||
| { | |||
| cancelPendingUpdate(); | |||
| stopThread (10000); | |||
| disconnect(); | |||
| } | |||
| @@ -61,13 +61,13 @@ | |||
| #include "broadcasters/juce_ActionBroadcaster.cpp" | |||
| #include "broadcasters/juce_AsyncUpdater.cpp" | |||
| #include "broadcasters/juce_ChangeBroadcaster.cpp" | |||
| // #include "timers/juce_MultiTimer.cpp" | |||
| #include "timers/juce_MultiTimer.cpp" | |||
| #include "timers/juce_Timer.cpp" | |||
| // #include "interprocess/juce_InterprocessConnection.cpp" | |||
| // #include "interprocess/juce_InterprocessConnectionServer.cpp" | |||
| // #include "interprocess/juce_ConnectedChildProcess.cpp" | |||
| // #include "interprocess/juce_NetworkServiceDiscovery.cpp" | |||
| // #include "native/juce_ScopedLowPowerModeDisabler.cpp" | |||
| #include "interprocess/juce_InterprocessConnection.cpp" | |||
| #include "interprocess/juce_InterprocessConnectionServer.cpp" | |||
| #include "interprocess/juce_ConnectedChildProcess.cpp" | |||
| #include "interprocess/juce_NetworkServiceDiscovery.cpp" | |||
| #include "native/juce_ScopedLowPowerModeDisabler.cpp" | |||
| //============================================================================== | |||
| #if JUCE_MAC || JUCE_IOS | |||
| @@ -32,7 +32,7 @@ | |||
| ID: juce_events | |||
| vendor: juce | |||
| version: 6.1.6 | |||
| version: 7.0.1 | |||
| name: JUCE message and event handling classes | |||
| description: Classes for running an application's main event loop and sending/receiving messages, timers, etc. | |||
| website: http://www.juce.com/juce | |||
| @@ -26,6 +26,8 @@ namespace juce | |||
| MessageManager::MessageManager() noexcept | |||
| : messageThreadId (Thread::getCurrentThreadId()) | |||
| { | |||
| JUCE_VERSION_ID | |||
| if (JUCEApplicationBase::isStandaloneApp()) | |||
| Thread::setCurrentThreadName ("JUCE Message Thread"); | |||
| } | |||
| @@ -1,302 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| namespace Android | |||
| { | |||
| class Runnable : public juce::AndroidInterfaceImplementer | |||
| { | |||
| public: | |||
| virtual void run() = 0; | |||
| private: | |||
| jobject invoke (jobject proxy, jobject method, jobjectArray args) override | |||
| { | |||
| auto* env = getEnv(); | |||
| auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName)); | |||
| if (methodName == "run") | |||
| { | |||
| run(); | |||
| return nullptr; | |||
| } | |||
| // invoke base class | |||
| return AndroidInterfaceImplementer::invoke (proxy, method, args); | |||
| } | |||
| }; | |||
| struct Handler | |||
| { | |||
| Handler() : nativeHandler (LocalRef<jobject> (getEnv()->NewObject (AndroidHandler, AndroidHandler.constructor))) {} | |||
| ~Handler() { clearSingletonInstance(); } | |||
| JUCE_DECLARE_SINGLETON (Handler, false) | |||
| bool post (jobject runnable) | |||
| { | |||
| return (getEnv()->CallBooleanMethod (nativeHandler.get(), AndroidHandler.post, runnable) != 0); | |||
| } | |||
| GlobalRef nativeHandler; | |||
| }; | |||
| JUCE_IMPLEMENT_SINGLETON (Handler) | |||
| } | |||
| //============================================================================== | |||
| struct AndroidMessageQueue : private Android::Runnable | |||
| { | |||
| JUCE_DECLARE_SINGLETON_SINGLETHREADED (AndroidMessageQueue, true) | |||
| AndroidMessageQueue() | |||
| : self (CreateJavaInterface (this, "java/lang/Runnable")) | |||
| { | |||
| } | |||
| ~AndroidMessageQueue() override | |||
| { | |||
| JUCE_ASSERT_MESSAGE_THREAD | |||
| clearSingletonInstance(); | |||
| } | |||
| bool post (MessageManager::MessageBase::Ptr&& message) | |||
| { | |||
| queue.add (std::move (message)); | |||
| // this will call us on the message thread | |||
| return handler.post (self.get()); | |||
| } | |||
| private: | |||
| void run() override | |||
| { | |||
| for (;;) | |||
| { | |||
| MessageManager::MessageBase::Ptr message (queue.removeAndReturn (0)); | |||
| if (message == nullptr) | |||
| break; | |||
| message->messageCallback(); | |||
| } | |||
| } | |||
| // the this pointer to this class in Java land | |||
| GlobalRef self; | |||
| ReferenceCountedArray<MessageManager::MessageBase, CriticalSection> queue; | |||
| Android::Handler handler; | |||
| }; | |||
| JUCE_IMPLEMENT_SINGLETON (AndroidMessageQueue) | |||
| //============================================================================== | |||
| void MessageManager::doPlatformSpecificInitialisation() { AndroidMessageQueue::getInstance(); } | |||
| void MessageManager::doPlatformSpecificShutdown() { AndroidMessageQueue::deleteInstance(); } | |||
| bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||
| { | |||
| return AndroidMessageQueue::getInstance()->post (message); | |||
| } | |||
| //============================================================================== | |||
| void MessageManager::broadcastMessage (const String&) | |||
| { | |||
| } | |||
| void MessageManager::runDispatchLoop() | |||
| { | |||
| } | |||
| void MessageManager::stopDispatchLoop() | |||
| { | |||
| struct QuitCallback : public CallbackMessage | |||
| { | |||
| QuitCallback() {} | |||
| void messageCallback() override | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> activity (getCurrentActivity()); | |||
| if (activity != nullptr) | |||
| { | |||
| jmethodID quitMethod = env->GetMethodID (AndroidActivity, "finishAndRemoveTask", "()V"); | |||
| if (quitMethod != nullptr) | |||
| { | |||
| env->CallVoidMethod (activity.get(), quitMethod); | |||
| return; | |||
| } | |||
| quitMethod = env->GetMethodID (AndroidActivity, "finish", "()V"); | |||
| jassert (quitMethod != nullptr); | |||
| env->CallVoidMethod (activity.get(), quitMethod); | |||
| } | |||
| else | |||
| { | |||
| jassertfalse; | |||
| } | |||
| } | |||
| }; | |||
| (new QuitCallback())->post(); | |||
| quitMessagePosted = true; | |||
| } | |||
| //============================================================================== | |||
| class JuceAppLifecycle : public ActivityLifecycleCallbacks | |||
| { | |||
| public: | |||
| JuceAppLifecycle (juce::JUCEApplicationBase* (*initSymbolAddr)()) | |||
| : createApplicationSymbol (initSymbolAddr) | |||
| { | |||
| LocalRef<jobject> appContext (getAppContext()); | |||
| if (appContext != nullptr) | |||
| { | |||
| auto* env = getEnv(); | |||
| myself = GlobalRef (CreateJavaInterface (this, "android/app/Application$ActivityLifecycleCallbacks")); | |||
| env->CallVoidMethod (appContext.get(), AndroidApplication.registerActivityLifecycleCallbacks, myself.get()); | |||
| } | |||
| } | |||
| ~JuceAppLifecycle() override | |||
| { | |||
| LocalRef<jobject> appContext (getAppContext()); | |||
| if (appContext != nullptr && myself != nullptr) | |||
| { | |||
| auto* env = getEnv(); | |||
| clear(); | |||
| env->CallVoidMethod (appContext.get(), AndroidApplication.unregisterActivityLifecycleCallbacks, myself.get()); | |||
| myself.clear(); | |||
| } | |||
| } | |||
| void onActivityCreated (jobject, jobject) override | |||
| { | |||
| checkCreated(); | |||
| } | |||
| void onActivityDestroyed (jobject activity) override | |||
| { | |||
| auto* env = getEnv(); | |||
| // if the main activity is being destroyed, only then tear-down JUCE | |||
| if (env->IsSameObject (getMainActivity().get(), activity) != 0) | |||
| { | |||
| JUCEApplicationBase::appWillTerminateByForce(); | |||
| JNIClassBase::releaseAllClasses (env); | |||
| jclass systemClass = (jclass) env->FindClass ("java/lang/System"); | |||
| jmethodID exitMethod = env->GetStaticMethodID (systemClass, "exit", "(I)V"); | |||
| env->CallStaticVoidMethod (systemClass, exitMethod, 0); | |||
| } | |||
| } | |||
| void onActivityStarted (jobject) override | |||
| { | |||
| checkCreated(); | |||
| } | |||
| void onActivityPaused (jobject) override | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| app->suspended(); | |||
| } | |||
| void onActivityResumed (jobject) override | |||
| { | |||
| checkInitialised(); | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| app->resumed(); | |||
| } | |||
| static JuceAppLifecycle& getInstance (juce::JUCEApplicationBase* (*initSymbolAddr)()) | |||
| { | |||
| static JuceAppLifecycle juceAppLifecycle (initSymbolAddr); | |||
| return juceAppLifecycle; | |||
| } | |||
| private: | |||
| void checkCreated() | |||
| { | |||
| if (JUCEApplicationBase::getInstance() == nullptr) | |||
| { | |||
| DBG (SystemStats::getJUCEVersion()); | |||
| JUCEApplicationBase::createInstance = createApplicationSymbol; | |||
| initialiseJuce_GUI(); | |||
| if (! JUCEApplicationBase::createInstance()) | |||
| jassertfalse; // you must supply an application object for an android app! | |||
| jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
| } | |||
| } | |||
| void checkInitialised() | |||
| { | |||
| checkCreated(); | |||
| if (! hasBeenInitialised) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| { | |||
| hasBeenInitialised = app->initialiseApp(); | |||
| if (! hasBeenInitialised) | |||
| exit (app->shutdownApp()); | |||
| } | |||
| } | |||
| } | |||
| GlobalRef myself; | |||
| juce::JUCEApplicationBase* (*createApplicationSymbol)(); | |||
| bool hasBeenInitialised = false; | |||
| }; | |||
| //============================================================================== | |||
| File juce_getExecutableFile(); | |||
| void juce_juceEventsAndroidStartApp(); | |||
| void juce_juceEventsAndroidStartApp() | |||
| { | |||
| auto dllPath = juce_getExecutableFile().getFullPathName(); | |||
| auto addr = reinterpret_cast<juce::JUCEApplicationBase*(*)()> (DynamicLibrary (dllPath) | |||
| .getFunction ("juce_CreateApplication")); | |||
| if (addr != nullptr) | |||
| JuceAppLifecycle::getInstance (addr); | |||
| } | |||
| } // namespace juce | |||
| @@ -1,103 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| void MessageManager::runDispatchLoop() | |||
| { | |||
| jassert (isThisTheMessageThread()); // must only be called by the message thread | |||
| while (quitMessagePosted.get() == 0) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode | |||
| beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]]; | |||
| } | |||
| } | |||
| } | |||
| void MessageManager::stopDispatchLoop() | |||
| { | |||
| if (! SystemStats::isRunningInAppExtensionSandbox()) | |||
| [[[UIApplication sharedApplication] delegate] applicationWillTerminate: [UIApplication sharedApplication]]; | |||
| exit (0); // iOS apps get no mercy.. | |||
| } | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| jassert (isThisTheMessageThread()); // must only be called by the message thread | |||
| uint32 startTime = Time::getMillisecondCounter(); | |||
| NSDate* endDate = [NSDate dateWithTimeIntervalSinceNow: millisecondsToRunFor * 0.001]; | |||
| while (quitMessagePosted.get() == 0) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode | |||
| beforeDate: endDate]; | |||
| if (millisecondsToRunFor >= 0 | |||
| && Time::getMillisecondCounter() >= startTime + (uint32) millisecondsToRunFor) | |||
| break; | |||
| } | |||
| } | |||
| return quitMessagePosted.get() == 0; | |||
| } | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| static std::unique_ptr<MessageQueue> messageQueue; | |||
| void MessageManager::doPlatformSpecificInitialisation() | |||
| { | |||
| if (messageQueue == nullptr) | |||
| messageQueue.reset (new MessageQueue()); | |||
| } | |||
| void MessageManager::doPlatformSpecificShutdown() | |||
| { | |||
| messageQueue = nullptr; | |||
| } | |||
| bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||
| { | |||
| if (messageQueue != nullptr) | |||
| messageQueue->post (message); | |||
| return true; | |||
| } | |||
| void MessageManager::broadcastMessage (const String&) | |||
| { | |||
| // N/A on current iOS | |||
| } | |||
| } // namespace juce | |||
| @@ -116,11 +116,11 @@ | |||
| #include "geometry/juce_PathStrokeType.cpp" | |||
| #include "placement/juce_RectanglePlacement.cpp" | |||
| #include "contexts/juce_GraphicsContext.cpp" | |||
| // #include "contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp" | |||
| #include "contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp" | |||
| #include "contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" | |||
| #include "images/juce_Image.cpp" | |||
| // #include "images/juce_ImageCache.cpp" | |||
| // #include "images/juce_ImageConvolutionKernel.cpp" | |||
| #include "images/juce_ImageCache.cpp" | |||
| #include "images/juce_ImageConvolutionKernel.cpp" | |||
| #include "images/juce_ImageFileFormat.cpp" | |||
| #include "image_formats/juce_GIFLoader.cpp" | |||
| #include "image_formats/juce_JPEGLoader.cpp" | |||
| @@ -132,7 +132,7 @@ | |||
| #include "fonts/juce_GlyphArrangement.cpp" | |||
| #include "fonts/juce_TextLayout.cpp" | |||
| #include "effects/juce_DropShadowEffect.cpp" | |||
| // #include "effects/juce_GlowEffect.cpp" | |||
| #include "effects/juce_GlowEffect.cpp" | |||
| #if JUCE_UNIT_TESTS | |||
| #include "geometry/juce_Rectangle_test.cpp" | |||
| @@ -35,7 +35,7 @@ | |||
| ID: juce_graphics | |||
| vendor: juce | |||
| version: 6.1.6 | |||
| version: 7.0.1 | |||
| name: JUCE graphics classes | |||
| description: Classes for 2D vector graphics, image loading/saving, font handling, etc. | |||
| website: http://www.juce.com/juce | |||
| @@ -1,549 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 7 End-User License | |||
| Agreement and JUCE Privacy Policy. | |||
| End User License Agreement: www.juce.com/juce-7-licence | |||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| struct DefaultFontNames | |||
| { | |||
| DefaultFontNames() | |||
| : defaultSans ("sans"), | |||
| defaultSerif ("serif"), | |||
| defaultFixed ("monospace"), | |||
| defaultFallback ("sans") | |||
| { | |||
| } | |||
| String getRealFontName (const String& faceName) const | |||
| { | |||
| if (faceName == Font::getDefaultSansSerifFontName()) return defaultSans; | |||
| if (faceName == Font::getDefaultSerifFontName()) return defaultSerif; | |||
| if (faceName == Font::getDefaultMonospacedFontName()) return defaultFixed; | |||
| return faceName; | |||
| } | |||
| String defaultSans, defaultSerif, defaultFixed, defaultFallback; | |||
| }; | |||
| Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) | |||
| { | |||
| static DefaultFontNames defaultNames; | |||
| Font f (font); | |||
| f.setTypefaceName (defaultNames.getRealFontName (font.getTypefaceName())); | |||
| return Typeface::createSystemTypefaceFor (f); | |||
| } | |||
| //============================================================================== | |||
| #if JUCE_USE_FREETYPE | |||
| StringArray FTTypefaceList::getDefaultFontDirectories() | |||
| { | |||
| return StringArray ("/system/fonts"); | |||
| } | |||
| Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) | |||
| { | |||
| return new FreeTypeTypeface (font); | |||
| } | |||
| void Typeface::scanFolderForFonts (const File& folder) | |||
| { | |||
| FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName())); | |||
| } | |||
| StringArray Font::findAllTypefaceNames() | |||
| { | |||
| return FTTypefaceList::getInstance()->findAllFamilyNames(); | |||
| } | |||
| StringArray Font::findAllTypefaceStyles (const String& family) | |||
| { | |||
| return FTTypefaceList::getInstance()->findAllTypefaceStyles (family); | |||
| } | |||
| bool TextLayout::createNativeLayout (const AttributedString&) | |||
| { | |||
| return false; | |||
| } | |||
| #else | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (create, "create", "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \ | |||
| STATICMETHOD (createFromFile, "createFromFile", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ | |||
| STATICMETHOD (createFromAsset, "createFromAsset", "(Landroid/content/res/AssetManager;Ljava/lang/String;)Landroid/graphics/Typeface;") | |||
| DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (constructor, "<init>", "()V") \ | |||
| METHOD (computeBounds, "computeBounds", "(Landroid/graphics/RectF;Z)V") | |||
| DECLARE_JNI_CLASS (AndroidPath, "android/graphics/Path") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (constructor, "<init>", "()V") \ | |||
| FIELD (left, "left", "F") \ | |||
| FIELD (right, "right", "F") \ | |||
| FIELD (top, "top", "F") \ | |||
| FIELD (bottom, "bottom", "F") \ | |||
| METHOD (roundOut, "roundOut", "(Landroid/graphics/Rect;)V") | |||
| DECLARE_JNI_CLASS (AndroidRectF, "android/graphics/RectF") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (getInstance, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;") \ | |||
| METHOD (update, "update", "([B)V") \ | |||
| METHOD (digest, "digest", "()[B") | |||
| DECLARE_JNI_CLASS (JavaMessageDigest, "java/security/MessageDigest") | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| StringArray Font::findAllTypefaceNames() | |||
| { | |||
| StringArray results; | |||
| for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, "*.ttf")) | |||
| results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().upToLastOccurrenceOf ("-", false, false)); | |||
| return results; | |||
| } | |||
| StringArray Font::findAllTypefaceStyles (const String& family) | |||
| { | |||
| StringArray results ("Regular"); | |||
| for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, family + "-*.ttf")) | |||
| results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().fromLastOccurrenceOf ("-", false, false)); | |||
| return results; | |||
| } | |||
| const float referenceFontSize = 256.0f; | |||
| const float referenceFontToUnits = 1.0f / referenceFontSize; | |||
| //============================================================================== | |||
| class AndroidTypeface : public Typeface | |||
| { | |||
| public: | |||
| AndroidTypeface (const Font& font) | |||
| : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), | |||
| ascent (0), descent (0), heightToPointsFactor (1.0f) | |||
| { | |||
| JNIEnv* const env = getEnv(); | |||
| // First check whether there's an embedded asset with this font name: | |||
| typeface = GlobalRef (getTypefaceFromAsset (name)); | |||
| if (typeface.get() == nullptr) | |||
| { | |||
| const bool isBold = style.contains ("Bold"); | |||
| const bool isItalic = style.contains ("Italic"); | |||
| File fontFile (getFontFile (name, style)); | |||
| if (! fontFile.exists()) | |||
| fontFile = findFontFile (name, isBold, isItalic); | |||
| if (fontFile.exists()) | |||
| typeface = GlobalRef (LocalRef<jobject>(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile, | |||
| javaString (fontFile.getFullPathName()).get()))); | |||
| else | |||
| typeface = GlobalRef (LocalRef<jobject>(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.create, | |||
| javaString (getName()).get(), | |||
| (isBold ? 1 : 0) + (isItalic ? 2 : 0)))); | |||
| } | |||
| initialise (env); | |||
| } | |||
| AndroidTypeface (const void* data, size_t size) | |||
| : Typeface (String (static_cast<uint64> (reinterpret_cast<uintptr_t> (data))), String()) | |||
| { | |||
| auto* env = getEnv(); | |||
| auto cacheFile = getCacheFileForData (data, size); | |||
| typeface = GlobalRef (LocalRef<jobject>(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile, | |||
| javaString (cacheFile.getFullPathName()).get()))); | |||
| initialise (env); | |||
| } | |||
| void initialise (JNIEnv* const env) | |||
| { | |||
| rect = GlobalRef (LocalRef<jobject>(env->NewObject (AndroidRect, AndroidRect.constructor, 0, 0, 0, 0))); | |||
| paint = GlobalRef (GraphicsHelpers::createPaint (Graphics::highResamplingQuality)); | |||
| const LocalRef<jobject> ignored (paint.callObjectMethod (AndroidPaint.setTypeface, typeface.get())); | |||
| charArray = GlobalRef (LocalRef<jobject>((jobject) env->NewCharArray (2))); | |||
| paint.callVoidMethod (AndroidPaint.setTextSize, referenceFontSize); | |||
| const float fullAscent = std::abs (paint.callFloatMethod (AndroidPaint.ascent)); | |||
| const float fullDescent = paint.callFloatMethod (AndroidPaint.descent); | |||
| const float totalHeight = fullAscent + fullDescent; | |||
| ascent = fullAscent / totalHeight; | |||
| descent = fullDescent / totalHeight; | |||
| heightToPointsFactor = referenceFontSize / totalHeight; | |||
| } | |||
| float getAscent() const override { return ascent; } | |||
| float getDescent() const override { return descent; } | |||
| float getHeightToPointsFactor() const override { return heightToPointsFactor; } | |||
| float getStringWidth (const String& text) override | |||
| { | |||
| JNIEnv* env = getEnv(); | |||
| auto numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer()); | |||
| jfloatArray widths = env->NewFloatArray ((int) numChars); | |||
| const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths); | |||
| HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone)); | |||
| env->GetFloatArrayRegion (widths, 0, numDone, localWidths); | |||
| env->DeleteLocalRef (widths); | |||
| float x = 0; | |||
| for (int i = 0; i < numDone; ++i) | |||
| x += localWidths[i]; | |||
| return x * referenceFontToUnits; | |||
| } | |||
| void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) override | |||
| { | |||
| JNIEnv* env = getEnv(); | |||
| auto numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer()); | |||
| jfloatArray widths = env->NewFloatArray ((int) numChars); | |||
| const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths); | |||
| HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone)); | |||
| env->GetFloatArrayRegion (widths, 0, numDone, localWidths); | |||
| env->DeleteLocalRef (widths); | |||
| auto s = text.getCharPointer(); | |||
| xOffsets.add (0); | |||
| float x = 0; | |||
| for (int i = 0; i < numDone; ++i) | |||
| { | |||
| const float local = localWidths[i]; | |||
| // Android uses jchar (UTF-16) characters | |||
| jchar ch = (jchar) s.getAndAdvance(); | |||
| // Android has no proper glyph support, so we have to do | |||
| // a hacky workaround for ligature detection | |||
| #if JUCE_STRING_UTF_TYPE <= 16 | |||
| static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two java chars in one glyph"); | |||
| // if the width of this glyph is zero inside the string but has | |||
| // a width on it's own, then it's probably due to ligature | |||
| if (local == 0.0f && glyphs.size() > 0 && getStringWidth (String (ch)) > 0.0f) | |||
| { | |||
| // modify the previous glyph | |||
| int& glyphNumber = glyphs.getReference (glyphs.size() - 1); | |||
| // make sure this is not a three character ligature | |||
| if (glyphNumber < std::numeric_limits<jchar>::max()) | |||
| { | |||
| const unsigned int previousGlyph | |||
| = static_cast<unsigned int> (glyphNumber) & ((1U << (sizeof (jchar) * 8U)) - 1U); | |||
| const unsigned int thisGlyph | |||
| = static_cast<unsigned int> (ch) & ((1U << (sizeof (jchar) * 8U)) - 1U); | |||
| glyphNumber = static_cast<int> ((thisGlyph << (sizeof (jchar) * 8U)) | previousGlyph); | |||
| ch = 0; | |||
| } | |||
| } | |||
| #endif | |||
| glyphs.add ((int) ch); | |||
| x += local; | |||
| xOffsets.add (x * referenceFontToUnits); | |||
| } | |||
| } | |||
| bool getOutlineForGlyph (int /*glyphNumber*/, Path& /*destPath*/) override | |||
| { | |||
| return false; | |||
| } | |||
| EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& t, float /*fontHeight*/) override | |||
| { | |||
| #if JUCE_STRING_UTF_TYPE <= 16 | |||
| static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two jni chars in one int"); | |||
| // glyphNumber of zero is used to indicate that the last character was a ligature | |||
| if (glyphNumber == 0) return nullptr; | |||
| jchar ch1 = (static_cast<unsigned int> (glyphNumber) >> 0) & ((1U << (sizeof (jchar) * 8U)) - 1U); | |||
| jchar ch2 = (static_cast<unsigned int> (glyphNumber) >> (sizeof (jchar) * 8U)) & ((1U << (sizeof (jchar) * 8U)) - 1U); | |||
| #else | |||
| jchar ch1 = glyphNumber, ch2 = 0; | |||
| #endif | |||
| Rectangle<int> bounds; | |||
| auto* env = getEnv(); | |||
| { | |||
| LocalRef<jobject> matrix (GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t))); | |||
| jboolean isCopy; | |||
| auto* buffer = env->GetCharArrayElements ((jcharArray) charArray.get(), &isCopy); | |||
| buffer[0] = ch1; buffer[1] = ch2; | |||
| env->ReleaseCharArrayElements ((jcharArray) charArray.get(), buffer, 0); | |||
| LocalRef<jobject> path (env->NewObject (AndroidPath, AndroidPath.constructor)); | |||
| LocalRef<jobject> boundsF (env->NewObject (AndroidRectF, AndroidRectF.constructor)); | |||
| env->CallVoidMethod (paint.get(), AndroidPaint.getCharsPath, charArray.get(), 0, (ch2 != 0 ? 2 : 1), 0.0f, 0.0f, path.get()); | |||
| env->CallVoidMethod (path.get(), AndroidPath.computeBounds, boundsF.get(), 1); | |||
| env->CallBooleanMethod (matrix.get(), AndroidMatrix.mapRect, boundsF.get()); | |||
| env->CallVoidMethod (boundsF.get(), AndroidRectF.roundOut, rect.get()); | |||
| bounds = Rectangle<int>::leftTopRightBottom (env->GetIntField (rect.get(), AndroidRect.left) - 1, | |||
| env->GetIntField (rect.get(), AndroidRect.top), | |||
| env->GetIntField (rect.get(), AndroidRect.right) + 1, | |||
| env->GetIntField (rect.get(), AndroidRect.bottom)); | |||
| auto w = bounds.getWidth(); | |||
| auto h = jmax (1, bounds.getHeight()); | |||
| LocalRef<jobject> bitmapConfig (env->CallStaticObjectMethod (AndroidBitmapConfig, AndroidBitmapConfig.valueOf, javaString ("ARGB_8888").get())); | |||
| LocalRef<jobject> bitmap (env->CallStaticObjectMethod (AndroidBitmap, AndroidBitmap.createBitmap, w, h, bitmapConfig.get())); | |||
| LocalRef<jobject> canvas (env->NewObject (AndroidCanvas, AndroidCanvas.create, bitmap.get())); | |||
| env->CallBooleanMethod (matrix.get(), AndroidMatrix.postTranslate, (float) -bounds.getX(), (float) -bounds.getY()); | |||
| env->CallVoidMethod (canvas.get(), AndroidCanvas.setMatrix, matrix.get()); | |||
| env->CallVoidMethod (canvas.get(), AndroidCanvas.drawPath, path.get(), paint.get()); | |||
| int requiredRenderArraySize = w * h; | |||
| if (requiredRenderArraySize > lastCachedRenderArraySize) | |||
| { | |||
| cachedRenderArray = GlobalRef (LocalRef<jobject> ((jobject) env->NewIntArray (requiredRenderArraySize))); | |||
| lastCachedRenderArraySize = requiredRenderArraySize; | |||
| } | |||
| env->CallVoidMethod (bitmap.get(), AndroidBitmap.getPixels, cachedRenderArray.get(), 0, w, 0, 0, w, h); | |||
| env->CallVoidMethod (bitmap.get(), AndroidBitmap.recycle); | |||
| } | |||
| EdgeTable* et = nullptr; | |||
| if (! bounds.isEmpty()) | |||
| { | |||
| et = new EdgeTable (bounds); | |||
| jint* const maskDataElements = env->GetIntArrayElements ((jintArray) cachedRenderArray.get(), nullptr); | |||
| const jint* mask = maskDataElements; | |||
| for (int y = bounds.getY(); y < bounds.getBottom(); ++y) | |||
| { | |||
| #if JUCE_LITTLE_ENDIAN | |||
| const uint8* const lineBytes = ((const uint8*) mask) + 3; | |||
| #else | |||
| const uint8* const lineBytes = (const uint8*) mask; | |||
| #endif | |||
| et->clipLineToMask (bounds.getX(), y, lineBytes, 4, bounds.getWidth()); | |||
| mask += bounds.getWidth(); | |||
| } | |||
| env->ReleaseIntArrayElements ((jintArray) cachedRenderArray.get(), maskDataElements, 0); | |||
| } | |||
| return et; | |||
| } | |||
| GlobalRef typeface, paint, rect, charArray, cachedRenderArray; | |||
| float ascent, descent, heightToPointsFactor; | |||
| int lastCachedRenderArraySize = -1; | |||
| private: | |||
| static File findFontFile (const String& family, | |||
| const bool bold, const bool italic) | |||
| { | |||
| File file; | |||
| if (bold || italic) | |||
| { | |||
| String suffix; | |||
| if (bold) suffix = "Bold"; | |||
| if (italic) suffix << "Italic"; | |||
| file = getFontFile (family, suffix); | |||
| if (file.exists()) | |||
| return file; | |||
| } | |||
| file = getFontFile (family, "Regular"); | |||
| if (! file.exists()) | |||
| file = getFontFile (family, String()); | |||
| return file; | |||
| } | |||
| static File getFontFile (const String& family, const String& fontStyle) | |||
| { | |||
| String path ("/system/fonts/" + family); | |||
| if (fontStyle.isNotEmpty()) | |||
| path << '-' << fontStyle; | |||
| return File (path + ".ttf"); | |||
| } | |||
| static LocalRef<jobject> getTypefaceFromAsset (const String& typefaceName) | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> assetManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getAssets)); | |||
| if (assetManager == nullptr) | |||
| return LocalRef<jobject>(); | |||
| auto assetTypeface = env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromAsset, assetManager.get(), | |||
| javaString ("fonts/" + typefaceName).get()); | |||
| // this may throw | |||
| if (env->ExceptionCheck() != 0) | |||
| { | |||
| env->ExceptionClear(); | |||
| return LocalRef<jobject>(); | |||
| } | |||
| return LocalRef<jobject> (assetTypeface); | |||
| } | |||
| static File getCacheDirectory() | |||
| { | |||
| static File result = []() | |||
| { | |||
| auto appContext = getAppContext(); | |||
| if (appContext != nullptr) | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> cacheFile (env->CallObjectMethod (appContext.get(), AndroidContext.getCacheDir)); | |||
| LocalRef<jstring> jPath ((jstring) env->CallObjectMethod (cacheFile.get(), JavaFile.getAbsolutePath)); | |||
| return File (juceString (env, jPath.get())); | |||
| } | |||
| jassertfalse; | |||
| return File(); | |||
| } (); | |||
| return result; | |||
| } | |||
| static HashMap<String, File>& getInMemoryFontCache() | |||
| { | |||
| static HashMap<String, File> cache; | |||
| return cache; | |||
| } | |||
| static File getCacheFileForData (const void* data, size_t size) | |||
| { | |||
| static CriticalSection cs; | |||
| JNIEnv* const env = getEnv(); | |||
| String key; | |||
| { | |||
| LocalRef<jobject> digest (env->CallStaticObjectMethod (JavaMessageDigest, JavaMessageDigest.getInstance, javaString("MD5").get())); | |||
| LocalRef<jbyteArray> bytes(env->NewByteArray ((int) size)); | |||
| jboolean ignore; | |||
| auto* jbytes = env->GetByteArrayElements(bytes.get(), &ignore); | |||
| memcpy(jbytes, data, size); | |||
| env->ReleaseByteArrayElements(bytes.get(), jbytes, 0); | |||
| env->CallVoidMethod(digest.get(), JavaMessageDigest.update, bytes.get()); | |||
| LocalRef<jbyteArray> result((jbyteArray) env->CallObjectMethod(digest.get(), JavaMessageDigest.digest)); | |||
| auto* md5Bytes = env->GetByteArrayElements(result.get(), &ignore); | |||
| key = String::toHexString(md5Bytes, env->GetArrayLength(result.get()), 0); | |||
| env->ReleaseByteArrayElements(result.get(), md5Bytes, 0); | |||
| } | |||
| ScopedLock lock (cs); | |||
| auto& mapEntry = getInMemoryFontCache().getReference (key); | |||
| if (mapEntry == File()) | |||
| { | |||
| mapEntry = getCacheDirectory().getChildFile ("bindata_" + key); | |||
| mapEntry.replaceWithData (data, size); | |||
| } | |||
| return mapEntry; | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidTypeface) | |||
| }; | |||
| //============================================================================== | |||
| Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) | |||
| { | |||
| return new AndroidTypeface (font); | |||
| } | |||
| Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size) | |||
| { | |||
| return new AndroidTypeface (data, size); | |||
| } | |||
| void Typeface::scanFolderForFonts (const File&) | |||
| { | |||
| jassertfalse; // not available unless using FreeType | |||
| } | |||
| bool TextLayout::createNativeLayout (const AttributedString&) | |||
| { | |||
| return false; | |||
| } | |||
| #endif | |||
| } // namespace juce | |||
| @@ -1,66 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 7 End-User License | |||
| Agreement and JUCE Privacy Policy. | |||
| End User License Agreement: www.juce.com/juce-7-licence | |||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| namespace GraphicsHelpers | |||
| { | |||
| static LocalRef<jobject> createPaint (Graphics::ResamplingQuality quality) | |||
| { | |||
| jint constructorFlags = 1 /*ANTI_ALIAS_FLAG*/ | |||
| | 4 /*DITHER_FLAG*/ | |||
| | 128 /*SUBPIXEL_TEXT_FLAG*/; | |||
| if (quality > Graphics::lowResamplingQuality) | |||
| constructorFlags |= 2; /*FILTER_BITMAP_FLAG*/ | |||
| return LocalRef<jobject>(getEnv()->NewObject (AndroidPaint, AndroidPaint.constructor, constructorFlags)); | |||
| } | |||
| static LocalRef<jobject> createMatrix (JNIEnv* env, const AffineTransform& t) | |||
| { | |||
| auto m = LocalRef<jobject>(env->NewObject (AndroidMatrix, AndroidMatrix.constructor)); | |||
| jfloat values[9] = { t.mat00, t.mat01, t.mat02, | |||
| t.mat10, t.mat11, t.mat12, | |||
| 0.0f, 0.0f, 1.0f }; | |||
| jfloatArray javaArray = env->NewFloatArray (9); | |||
| env->SetFloatArrayRegion (javaArray, 0, 9, values); | |||
| env->CallVoidMethod (m, AndroidMatrix.setValues, javaArray); | |||
| env->DeleteLocalRef (javaArray); | |||
| return m; | |||
| } | |||
| } // namespace GraphicsHelpers | |||
| ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const | |||
| { | |||
| return SoftwareImageType().create (format, width, height, clearImage); | |||
| } | |||
| } // namespace juce | |||
| @@ -66,6 +66,9 @@ public: | |||
| /** Returns a section of text. */ | |||
| virtual String getText (Range<int> range) const = 0; | |||
| /** Returns the full text. */ | |||
| String getAllText() const { return getText ({ 0, getTotalNumCharacters() }); } | |||
| /** Replaces the text with a new string. */ | |||
| virtual void setText (const String& newText) = 0; | |||
| @@ -63,7 +63,6 @@ AccessibilityHandler::AccessibilityHandler (Component& comp, | |||
| interfaces (std::move (interfacesIn)), | |||
| nativeImpl (createNativeImpl (*this)) | |||
| { | |||
| notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementCreated); | |||
| } | |||
| AccessibilityHandler::~AccessibilityHandler() | |||
| @@ -327,7 +327,7 @@ struct Component::ComponentHelpers | |||
| static bool hitTest (Component& comp, Point<float> localPoint) | |||
| { | |||
| const auto intPoint = localPoint.roundToInt(); | |||
| return Rectangle<int> { comp.getWidth(), comp.getHeight() }.toFloat().contains (localPoint) | |||
| return Rectangle<int> { comp.getWidth(), comp.getHeight() }.contains (intPoint) | |||
| && comp.hitTest (intPoint.x, intPoint.y); | |||
| } | |||
| @@ -2943,6 +2943,11 @@ void Component::takeKeyboardFocus (FocusChangeType cause) | |||
| return; | |||
| WeakReference<Component> componentLosingFocus (currentlyFocusedComponent); | |||
| if (auto* losingFocus = componentLosingFocus.get()) | |||
| if (auto* otherPeer = losingFocus->getPeer()) | |||
| otherPeer->closeInputMethodContext(); | |||
| currentlyFocusedComponent = this; | |||
| Desktop::getInstance().triggerFocusCallback(); | |||
| @@ -3008,6 +3013,9 @@ void Component::giveAwayKeyboardFocusInternal (bool sendFocusLossEvent) | |||
| { | |||
| if (auto* componentLosingFocus = currentlyFocusedComponent) | |||
| { | |||
| if (auto* otherPeer = componentLosingFocus->getPeer()) | |||
| otherPeer->closeInputMethodContext(); | |||
| currentlyFocusedComponent = nullptr; | |||
| if (sendFocusLossEvent && componentLosingFocus != nullptr) | |||
| @@ -3300,6 +3308,18 @@ AccessibilityHandler* Component::getAccessibilityHandler() | |||
| || accessibilityHandler->getTypeIndex() != std::type_index (typeid (*this))) | |||
| { | |||
| accessibilityHandler = createAccessibilityHandler(); | |||
| // On Android, notifying that an element was created can cause the system to request | |||
| // the accessibility node info for the new element. If we're not careful, this will lead | |||
| // to recursive calls, as each time an element is created, new node info will be requested, | |||
| // causing an element to be created, causing a new info request... | |||
| // By assigning the accessibility handler before notifying the system that an element was | |||
| // created, the if() predicate above should evaluate to false on recursive calls, | |||
| // terminating the recursion. | |||
| if (accessibilityHandler != nullptr) | |||
| notifyAccessibilityEventInternal (*accessibilityHandler, InternalAccessibilityEvent::elementCreated); | |||
| else | |||
| jassertfalse; // createAccessibilityHandler must return non-null | |||
| } | |||
| return accessibilityHandler.get(); | |||
| @@ -45,6 +45,8 @@ | |||
| #include "juce_gui_basics.h" | |||
| #include <cctype> | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| #import <WebKit/WebKit.h> | |||
| @@ -66,9 +68,9 @@ | |||
| #include <commdlg.h> | |||
| #include <commctrl.h> | |||
| #include <sapi.h> | |||
| #include <Dxgi.h> | |||
| #if JUCE_MSVC | |||
| #include <Dxgi.h> | |||
| #include <UIAutomation.h> | |||
| #endif | |||
| @@ -143,105 +145,104 @@ namespace juce | |||
| #include "mouse/juce_ComponentDragger.cpp" | |||
| #include "mouse/juce_DragAndDropContainer.cpp" | |||
| #include "mouse/juce_MouseEvent.cpp" | |||
| // #include "mouse/juce_MouseInactivityDetector.cpp" | |||
| #include "mouse/juce_MouseInactivityDetector.cpp" | |||
| #include "mouse/juce_MouseListener.cpp" | |||
| #include "keyboard/juce_CaretComponent.cpp" | |||
| #include "keyboard/juce_KeyboardFocusTraverser.cpp" | |||
| #include "keyboard/juce_KeyListener.cpp" | |||
| #include "keyboard/juce_KeyPress.cpp" | |||
| #include "keyboard/juce_ModifierKeys.cpp" | |||
| // #include "buttons/juce_ArrowButton.cpp" | |||
| #include "buttons/juce_ArrowButton.cpp" | |||
| #include "buttons/juce_Button.cpp" | |||
| #include "buttons/juce_DrawableButton.cpp" | |||
| // #include "buttons/juce_HyperlinkButton.cpp" | |||
| // #include "buttons/juce_ImageButton.cpp" | |||
| // #include "buttons/juce_ShapeButton.cpp" | |||
| #include "buttons/juce_HyperlinkButton.cpp" | |||
| #include "buttons/juce_ImageButton.cpp" | |||
| #include "buttons/juce_ShapeButton.cpp" | |||
| #include "buttons/juce_TextButton.cpp" | |||
| // #include "buttons/juce_ToggleButton.cpp" | |||
| // #include "buttons/juce_ToolbarButton.cpp" | |||
| #include "buttons/juce_ToggleButton.cpp" | |||
| #include "buttons/juce_ToolbarButton.cpp" | |||
| #include "drawables/juce_Drawable.cpp" | |||
| #include "drawables/juce_DrawableComposite.cpp" | |||
| #include "drawables/juce_DrawableImage.cpp" | |||
| #include "drawables/juce_DrawablePath.cpp" | |||
| // #include "drawables/juce_DrawableRectangle.cpp" | |||
| #include "drawables/juce_DrawableRectangle.cpp" | |||
| #include "drawables/juce_DrawableShape.cpp" | |||
| #include "drawables/juce_DrawableText.cpp" | |||
| #include "drawables/juce_SVGParser.cpp" | |||
| #include "filebrowser/juce_DirectoryContentsDisplayComponent.cpp" | |||
| // #include "filebrowser/juce_DirectoryContentsList.cpp" | |||
| // #include "filebrowser/juce_FileBrowserComponent.cpp" | |||
| // #include "filebrowser/juce_FileChooser.cpp" | |||
| // #include "filebrowser/juce_FileChooserDialogBox.cpp" | |||
| // #include "filebrowser/juce_FileListComponent.cpp" | |||
| // #include "filebrowser/juce_FilenameComponent.cpp" | |||
| // #include "filebrowser/juce_FileSearchPathListComponent.cpp" | |||
| // #include "filebrowser/juce_FileTreeComponent.cpp" | |||
| // #include "filebrowser/juce_ImagePreviewComponent.cpp" | |||
| // #include "filebrowser/juce_ContentSharer.cpp" | |||
| #include "filebrowser/juce_DirectoryContentsList.cpp" | |||
| #include "filebrowser/juce_FileBrowserComponent.cpp" | |||
| #include "filebrowser/juce_FileChooser.cpp" | |||
| #include "filebrowser/juce_FileChooserDialogBox.cpp" | |||
| #include "filebrowser/juce_FileListComponent.cpp" | |||
| #include "filebrowser/juce_FilenameComponent.cpp" | |||
| #include "filebrowser/juce_FileSearchPathListComponent.cpp" | |||
| #include "filebrowser/juce_FileTreeComponent.cpp" | |||
| #include "filebrowser/juce_ImagePreviewComponent.cpp" | |||
| #include "filebrowser/juce_ContentSharer.cpp" | |||
| #include "layout/juce_ComponentAnimator.cpp" | |||
| #include "layout/juce_ComponentBoundsConstrainer.cpp" | |||
| // #include "layout/juce_ComponentBuilder.cpp" | |||
| #include "layout/juce_ComponentBuilder.cpp" | |||
| #include "layout/juce_ComponentMovementWatcher.cpp" | |||
| #include "layout/juce_ConcertinaPanel.cpp" | |||
| // #include "layout/juce_GroupComponent.cpp" | |||
| // #include "layout/juce_MultiDocumentPanel.cpp" | |||
| #include "layout/juce_GroupComponent.cpp" | |||
| #include "layout/juce_MultiDocumentPanel.cpp" | |||
| #include "layout/juce_ResizableBorderComponent.cpp" | |||
| #include "layout/juce_ResizableCornerComponent.cpp" | |||
| // #include "layout/juce_ResizableEdgeComponent.cpp" | |||
| #include "layout/juce_ResizableEdgeComponent.cpp" | |||
| #include "layout/juce_ScrollBar.cpp" | |||
| // #include "layout/juce_SidePanel.cpp" | |||
| // #include "layout/juce_StretchableLayoutManager.cpp" | |||
| // #include "layout/juce_StretchableLayoutResizerBar.cpp" | |||
| #include "layout/juce_SidePanel.cpp" | |||
| #include "layout/juce_StretchableLayoutManager.cpp" | |||
| #include "layout/juce_StretchableLayoutResizerBar.cpp" | |||
| #include "layout/juce_StretchableObjectResizer.cpp" | |||
| #include "layout/juce_TabbedButtonBar.cpp" | |||
| #include "layout/juce_TabbedComponent.cpp" | |||
| #include "layout/juce_Viewport.cpp" | |||
| #include "lookandfeel/juce_LookAndFeel.cpp" | |||
| #include "lookandfeel/juce_LookAndFeel_V2.cpp" | |||
| // #include "lookandfeel/juce_LookAndFeel_V1.cpp" | |||
| #include "lookandfeel/juce_LookAndFeel_V1.cpp" | |||
| #include "lookandfeel/juce_LookAndFeel_V3.cpp" | |||
| #include "lookandfeel/juce_LookAndFeel_V4.cpp" | |||
| #include "menus/juce_MenuBarComponent.cpp" | |||
| // #include "menus/juce_BurgerMenuComponent.cpp" | |||
| #include "menus/juce_BurgerMenuComponent.cpp" | |||
| #include "menus/juce_MenuBarModel.cpp" | |||
| #include "menus/juce_PopupMenu.cpp" | |||
| // #include "positioning/juce_MarkerList.cpp" | |||
| // #include "positioning/juce_RelativeCoordinate.cpp" | |||
| // #include "positioning/juce_RelativeCoordinatePositioner.cpp" | |||
| // #include "positioning/juce_RelativeParallelogram.cpp" | |||
| // #include "positioning/juce_RelativePoint.cpp" | |||
| // #include "positioning/juce_RelativePointPath.cpp" | |||
| // #include "positioning/juce_RelativeRectangle.cpp" | |||
| // #include "properties/juce_BooleanPropertyComponent.cpp" | |||
| // #include "properties/juce_ButtonPropertyComponent.cpp" | |||
| #include "positioning/juce_MarkerList.cpp" | |||
| #include "positioning/juce_RelativeCoordinate.cpp" | |||
| #include "positioning/juce_RelativeCoordinatePositioner.cpp" | |||
| #include "positioning/juce_RelativeParallelogram.cpp" | |||
| #include "positioning/juce_RelativePoint.cpp" | |||
| #include "positioning/juce_RelativePointPath.cpp" | |||
| #include "positioning/juce_RelativeRectangle.cpp" | |||
| #include "properties/juce_BooleanPropertyComponent.cpp" | |||
| #include "properties/juce_ButtonPropertyComponent.cpp" | |||
| #include "properties/juce_ChoicePropertyComponent.cpp" | |||
| #include "properties/juce_PropertyComponent.cpp" | |||
| // #include "properties/juce_PropertyPanel.cpp" | |||
| // #include "properties/juce_SliderPropertyComponent.cpp" | |||
| // #include "properties/juce_TextPropertyComponent.cpp" | |||
| // #include "properties/juce_MultiChoicePropertyComponent.cpp" | |||
| #include "properties/juce_PropertyPanel.cpp" | |||
| #include "properties/juce_SliderPropertyComponent.cpp" | |||
| #include "properties/juce_TextPropertyComponent.cpp" | |||
| #include "properties/juce_MultiChoicePropertyComponent.cpp" | |||
| #include "widgets/juce_ComboBox.cpp" | |||
| // #include "widgets/juce_ImageComponent.cpp" | |||
| #include "widgets/juce_ImageComponent.cpp" | |||
| #include "widgets/juce_Label.cpp" | |||
| // #include "widgets/juce_ListBox.cpp" | |||
| #include "widgets/juce_ListBox.cpp" | |||
| #include "widgets/juce_ProgressBar.cpp" | |||
| #include "widgets/juce_Slider.cpp" | |||
| #include "widgets/juce_TableHeaderComponent.cpp" | |||
| // #include "widgets/juce_TableListBox.cpp" | |||
| #include "widgets/juce_TableListBox.cpp" | |||
| #include "widgets/juce_TextEditor.cpp" | |||
| // #include "widgets/juce_ToolbarItemComponent.cpp" | |||
| // #include "widgets/juce_Toolbar.cpp" | |||
| // #include "widgets/juce_ToolbarItemPalette.cpp" | |||
| enum class Async { yes, no }; | |||
| // #include "widgets/juce_TreeView.cpp" | |||
| #include "widgets/juce_ToolbarItemComponent.cpp" | |||
| #include "widgets/juce_Toolbar.cpp" | |||
| #include "widgets/juce_ToolbarItemPalette.cpp" | |||
| #include "widgets/juce_TreeView.cpp" | |||
| #include "windows/juce_AlertWindow.cpp" | |||
| // #include "windows/juce_CallOutBox.cpp" | |||
| #include "windows/juce_CallOutBox.cpp" | |||
| #include "windows/juce_ComponentPeer.cpp" | |||
| #include "windows/juce_DialogWindow.cpp" | |||
| #include "windows/juce_DocumentWindow.cpp" | |||
| #include "windows/juce_ResizableWindow.cpp" | |||
| // #include "windows/juce_ThreadWithProgressWindow.cpp" | |||
| // #include "windows/juce_TooltipWindow.cpp" | |||
| #include "windows/juce_ThreadWithProgressWindow.cpp" | |||
| #include "windows/juce_TooltipWindow.cpp" | |||
| #include "windows/juce_TopLevelWindow.cpp" | |||
| #include "commands/juce_ApplicationCommandInfo.cpp" | |||
| #include "commands/juce_ApplicationCommandManager.cpp" | |||
| @@ -252,15 +253,15 @@ enum class Async { yes, no }; | |||
| #include "misc/juce_DropShadower.cpp" | |||
| #include "misc/juce_FocusOutline.cpp" | |||
| // #include "layout/juce_FlexBox.cpp" | |||
| // #include "layout/juce_GridItem.cpp" | |||
| // #include "layout/juce_Grid.cpp" | |||
| #include "layout/juce_FlexBox.cpp" | |||
| #include "layout/juce_GridItem.cpp" | |||
| #include "layout/juce_Grid.cpp" | |||
| #if JUCE_IOS || JUCE_WINDOWS | |||
| #include "native/juce_MultiTouchMapper.h" | |||
| #endif | |||
| #if JUCE_ANDROID || JUCE_WINDOWS | |||
| #if JUCE_ANDROID || JUCE_WINDOWS || JUCE_UNIT_TESTS | |||
| #include "native/accessibility/juce_AccessibilityTextHelpers.h" | |||
| #endif | |||
| @@ -271,7 +272,7 @@ enum class Async { yes, no }; | |||
| #include "native/accessibility/juce_ios_Accessibility.mm" | |||
| #include "native/juce_ios_UIViewComponentPeer.mm" | |||
| #include "native/juce_ios_Windowing.mm" | |||
| // #include "native/juce_ios_FileChooser.mm" | |||
| #include "native/juce_ios_FileChooser.mm" | |||
| #if JUCE_CONTENT_SHARING | |||
| #include "native/juce_ios_ContentSharer.cpp" | |||
| @@ -282,7 +283,7 @@ enum class Async { yes, no }; | |||
| #include "native/juce_mac_NSViewComponentPeer.mm" | |||
| #include "native/juce_mac_Windowing.mm" | |||
| #include "native/juce_mac_MainMenu.mm" | |||
| // #include "native/juce_mac_FileChooser.mm" | |||
| #include "native/juce_mac_FileChooser.mm" | |||
| #endif | |||
| #include "native/juce_mac_MouseCursor.mm" | |||
| @@ -299,7 +300,7 @@ enum class Async { yes, no }; | |||
| #endif | |||
| #include "native/juce_win32_Windowing.cpp" | |||
| #include "native/juce_win32_DragAndDrop.cpp" | |||
| // #include "native/juce_win32_FileChooser.cpp" | |||
| #include "native/juce_win32_FileChooser.cpp" | |||
| #elif JUCE_LINUX || JUCE_BSD | |||
| #include "native/x11/juce_linux_X11_Symbols.cpp" | |||
| @@ -312,13 +313,13 @@ enum class Async { yes, no }; | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| // #include "native/juce_linux_FileChooser.cpp" | |||
| #include "native/juce_linux_FileChooser.cpp" | |||
| #elif JUCE_ANDROID | |||
| #include "juce_core/files/juce_common_MimeTypes.h" | |||
| #include "native/accessibility/juce_android_Accessibility.cpp" | |||
| #include "native/juce_android_Windowing.cpp" | |||
| #include "native/juce_common_MimeTypes.cpp" | |||
| // #include "native/juce_android_FileChooser.cpp" | |||
| #include "native/juce_android_FileChooser.cpp" | |||
| #if JUCE_CONTENT_SHARING | |||
| #include "native/juce_android_ContentSharer.cpp" | |||
| @@ -412,3 +413,7 @@ bool juce::isWindowOnCurrentVirtualDesktop (void* x) | |||
| // Depends on types defined in platform-specific windowing files | |||
| #include "mouse/juce_MouseCursor.cpp" | |||
| #if JUCE_UNIT_TESTS | |||
| #include "native/accessibility/juce_AccessibilityTextHelpers_test.cpp" | |||
| #endif | |||
| @@ -35,7 +35,7 @@ | |||
| ID: juce_gui_basics | |||
| vendor: juce | |||
| version: 6.1.6 | |||
| version: 7.0.1 | |||
| name: JUCE GUI core classes | |||
| description: Basic user-interface components and related classes. | |||
| website: http://www.juce.com/juce | |||
| @@ -597,6 +597,11 @@ struct Grid::AutoPlacement | |||
| return referenceCell; | |||
| } | |||
| void updateMaxCrossDimensionFromAutoPlacementItem (int columnSpan, int rowSpan) | |||
| { | |||
| highestCrossDimension = jmax (highestCrossDimension, 1 + getCrossDimension ({ columnSpan, rowSpan })); | |||
| } | |||
| private: | |||
| struct SortableCell | |||
| { | |||
| @@ -642,9 +647,10 @@ struct Grid::AutoPlacement | |||
| bool isOutOfBounds (Cell cell, int columnSpan, int rowSpan) const | |||
| { | |||
| const auto crossSpan = columnFirst ? rowSpan : columnSpan; | |||
| const auto highestIndexOfCell = getCrossDimension (cell) + getCrossDimension ({ columnSpan, rowSpan }); | |||
| const auto highestIndexOfGrid = getHighestCrossDimension(); | |||
| return (getCrossDimension (cell) + crossSpan) > getHighestCrossDimension(); | |||
| return highestIndexOfGrid < highestIndexOfCell; | |||
| } | |||
| int getHighestCrossDimension() const | |||
| @@ -807,6 +813,11 @@ struct Grid::AutoPlacement | |||
| } | |||
| } | |||
| // https://www.w3.org/TR/css-grid-1/#auto-placement-algo step 3.3 | |||
| for (auto* item : sortedItems) | |||
| if (hasAutoPlacement (*item)) | |||
| plane.updateMaxCrossDimensionFromAutoPlacementItem (getSpanFromAuto (item->column), getSpanFromAuto (item->row)); | |||
| lastInsertionCell = { 1, 1 }; | |||
| for (auto* item : sortedItems) | |||
| @@ -61,6 +61,12 @@ struct Viewport::DragToScrollListener : private MouseListener, | |||
| Desktop::getInstance().removeGlobalMouseListener (this); | |||
| } | |||
| void stopOngoingAnimation() | |||
| { | |||
| offsetX.setPosition (offsetX.getPosition()); | |||
| offsetY.setPosition (offsetY.getPosition()); | |||
| } | |||
| void positionChanged (ViewportDragPosition&, double) override | |||
| { | |||
| viewport.setViewPosition (originalViewPos - Point<int> ((int) offsetX.getPosition(), | |||
| @@ -119,9 +125,11 @@ struct Viewport::DragToScrollListener : private MouseListener, | |||
| void endDragAndClearGlobalMouseListener() | |||
| { | |||
| offsetX.endDrag(); | |||
| offsetY.endDrag(); | |||
| isDragging = false; | |||
| if (std::exchange (isDragging, false) == true) | |||
| { | |||
| offsetX.endDrag(); | |||
| offsetY.endDrag(); | |||
| } | |||
| viewport.contentHolder.addMouseListener (this, true); | |||
| Desktop::getInstance().removeGlobalMouseListener (this); | |||
| @@ -229,6 +237,8 @@ void Viewport::recreateScrollbars() | |||
| getVerticalScrollBar().addListener (this); | |||
| getHorizontalScrollBar().addListener (this); | |||
| getVerticalScrollBar().addMouseListener (this, true); | |||
| getHorizontalScrollBar().addMouseListener (this, true); | |||
| resized(); | |||
| } | |||
| @@ -531,8 +541,15 @@ void Viewport::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRange | |||
| void Viewport::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel) | |||
| { | |||
| if (! useMouseWheelMoveIfNeeded (e, wheel)) | |||
| Component::mouseWheelMove (e, wheel); | |||
| if (e.eventComponent == this) | |||
| if (! useMouseWheelMoveIfNeeded (e, wheel)) | |||
| Component::mouseWheelMove (e, wheel); | |||
| } | |||
| void Viewport::mouseDown (const MouseEvent& e) | |||
| { | |||
| if (e.eventComponent == horizontalScrollBar.get() || e.eventComponent == verticalScrollBar.get()) | |||
| dragToScrollListener->stopOngoingAnimation(); | |||
| } | |||
| static int rescaleMouseWheelDistance (float distance, int singleStepSize) noexcept | |||
| @@ -318,6 +318,8 @@ public: | |||
| /** @internal */ | |||
| void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override; | |||
| /** @internal */ | |||
| void mouseDown (const MouseEvent& e) override; | |||
| /** @internal */ | |||
| bool keyPressed (const KeyPress&) override; | |||
| /** @internal */ | |||
| void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override; | |||
| @@ -337,8 +339,16 @@ protected: | |||
| private: | |||
| //============================================================================== | |||
| class AccessibilityIgnoredComponent : public Component | |||
| { | |||
| std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override | |||
| { | |||
| return createIgnoredAccessibilityHandler (*this); | |||
| } | |||
| }; | |||
| std::unique_ptr<ScrollBar> verticalScrollBar, horizontalScrollBar; | |||
| Component contentHolder; | |||
| AccessibilityIgnoredComponent contentHolder; | |||
| WeakReference<Component> contentComp; | |||
| Rectangle<int> lastVisibleArea; | |||
| int scrollBarThickness = 0; | |||
| @@ -87,7 +87,7 @@ struct HeaderItemComponent : public PopupMenu::CustomComponent | |||
| std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override | |||
| { | |||
| return nullptr; | |||
| return createIgnoredAccessibilityHandler (*this); | |||
| } | |||
| const Options& options; | |||
| @@ -275,7 +275,8 @@ private: | |||
| std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override | |||
| { | |||
| return item.isSeparator ? nullptr : std::make_unique<ItemAccessibilityHandler> (*this); | |||
| return item.isSeparator ? createIgnoredAccessibilityHandler (*this) | |||
| : std::make_unique<ItemAccessibilityHandler> (*this); | |||
| } | |||
| //============================================================================== | |||
| @@ -59,6 +59,7 @@ public: | |||
| startTimer (200); | |||
| setInterceptsMouseClicks (false, false); | |||
| setWantsKeyboardFocus (true); | |||
| setAlwaysOnTop (true); | |||
| } | |||
| @@ -200,7 +201,12 @@ public: | |||
| { | |||
| if (key == KeyPress::escapeKey) | |||
| { | |||
| dismissWithAnimation (true); | |||
| const auto wasVisible = isVisible(); | |||
| setVisible (false); | |||
| if (wasVisible) | |||
| dismissWithAnimation (true); | |||
| deleteSelf(); | |||
| return true; | |||
| } | |||
| @@ -466,8 +472,7 @@ void DragAndDropContainer::startDragging (const var& sourceDescription, | |||
| dragImageComponent->setOpaque (true); | |||
| dragImageComponent->addToDesktop (ComponentPeer::windowIgnoresMouseClicks | |||
| | ComponentPeer::windowIsTemporary | |||
| | ComponentPeer::windowIgnoresKeyPresses); | |||
| | ComponentPeer::windowIsTemporary); | |||
| } | |||
| else | |||
| { | |||
| @@ -484,6 +489,7 @@ void DragAndDropContainer::startDragging (const var& sourceDescription, | |||
| dragImageComponent->sourceDetails.localPosition = sourceComponent->getLocalPoint (nullptr, lastMouseDown); | |||
| dragImageComponent->updateLocation (false, lastMouseDown); | |||
| dragImageComponent->grabKeyboardFocus(); | |||
| #if JUCE_WINDOWS | |||
| // Under heavy load, the layered window's paint callback can often be lost by the OS, | |||
| @@ -26,8 +26,58 @@ | |||
| namespace juce | |||
| { | |||
| namespace AccessibilityTextHelpers | |||
| struct AccessibilityTextHelpers | |||
| { | |||
| /* Wraps a CharPtr into a stdlib-compatible iterator. | |||
| MSVC's std::reverse_iterator requires the wrapped iterator to be default constructible | |||
| when building in C++20 mode, but I don't really want to add public default constructors to | |||
| the CharPtr types. Instead, we add a very basic default constructor here which sets the | |||
| wrapped CharPtr to nullptr. | |||
| */ | |||
| template <typename CharPtr> | |||
| class CharPtrIteratorAdapter | |||
| { | |||
| public: | |||
| using difference_type = int; | |||
| using value_type = decltype (*std::declval<CharPtr>()); | |||
| using pointer = value_type*; | |||
| using reference = value_type; | |||
| using iterator_category = std::bidirectional_iterator_tag; | |||
| CharPtrIteratorAdapter() = default; | |||
| constexpr explicit CharPtrIteratorAdapter (CharPtr arg) : ptr (arg) {} | |||
| constexpr auto operator*() const { return *ptr; } | |||
| constexpr CharPtrIteratorAdapter& operator++() | |||
| { | |||
| ++ptr; | |||
| return *this; | |||
| } | |||
| constexpr CharPtrIteratorAdapter& operator--() | |||
| { | |||
| --ptr; | |||
| return *this; | |||
| } | |||
| constexpr bool operator== (const CharPtrIteratorAdapter& other) const { return ptr == other.ptr; } | |||
| constexpr bool operator!= (const CharPtrIteratorAdapter& other) const { return ptr != other.ptr; } | |||
| constexpr auto operator+ (difference_type offset) const { return CharPtrIteratorAdapter { ptr + offset }; } | |||
| constexpr auto operator- (difference_type offset) const { return CharPtrIteratorAdapter { ptr - offset }; } | |||
| private: | |||
| CharPtr ptr { {} }; | |||
| }; | |||
| template <typename CharPtr> | |||
| static auto makeCharPtrIteratorAdapter (CharPtr ptr) | |||
| { | |||
| return CharPtrIteratorAdapter<CharPtr> { ptr }; | |||
| } | |||
| enum class BoundaryType | |||
| { | |||
| character, | |||
| @@ -42,60 +92,177 @@ namespace AccessibilityTextHelpers | |||
| backwards | |||
| }; | |||
| static int findTextBoundary (const AccessibilityTextInterface& textInterface, | |||
| int currentPosition, | |||
| BoundaryType boundary, | |||
| Direction direction) | |||
| enum class ExtendSelection | |||
| { | |||
| const auto numCharacters = textInterface.getTotalNumCharacters(); | |||
| const auto isForwards = (direction == Direction::forwards); | |||
| const auto offsetWithDirection = [isForwards] (auto num) { return isForwards ? num : -num; }; | |||
| no, | |||
| yes | |||
| }; | |||
| switch (boundary) | |||
| /* Indicates whether a function may return the current text position, in the case that the | |||
| position already falls on a text unit boundary. | |||
| */ | |||
| enum class IncludeThisBoundary | |||
| { | |||
| no, //< Always search for the following boundary, even if the current position falls on a boundary | |||
| yes //< Return the current position if it falls on a boundary | |||
| }; | |||
| /* Indicates whether a word boundary should include any whitespaces that follow the | |||
| non-whitespace characters. | |||
| */ | |||
| enum class IncludeWhitespaceAfterWords | |||
| { | |||
| no, //< The word ends on the first whitespace character | |||
| yes //< The word ends after the last whitespace character | |||
| }; | |||
| /* Like std::distance, but always does an O(N) count rather than an O(1) count, and doesn't | |||
| require the iterators to have any member type aliases. | |||
| */ | |||
| template <typename Iter> | |||
| static int countDifference (Iter from, Iter to) | |||
| { | |||
| int distance = 0; | |||
| while (from != to) | |||
| { | |||
| case BoundaryType::character: | |||
| return jlimit (0, numCharacters, currentPosition + offsetWithDirection (1)); | |||
| ++from; | |||
| ++distance; | |||
| } | |||
| case BoundaryType::word: | |||
| case BoundaryType::line: | |||
| return distance; | |||
| } | |||
| /* Returns the number of characters between ptr and the next word end in a specific | |||
| direction. | |||
| If ptr is inside a word, the result will be the distance to the end of the same | |||
| word. | |||
| */ | |||
| template <typename CharPtr> | |||
| static int findNextWordEndOffset (CharPtr beginIn, | |||
| CharPtr endIn, | |||
| CharPtr ptrIn, | |||
| Direction direction, | |||
| IncludeThisBoundary includeBoundary, | |||
| IncludeWhitespaceAfterWords includeWhitespace) | |||
| { | |||
| const auto begin = makeCharPtrIteratorAdapter (beginIn); | |||
| const auto end = makeCharPtrIteratorAdapter (endIn); | |||
| const auto ptr = makeCharPtrIteratorAdapter (ptrIn); | |||
| const auto move = [&] (auto b, auto e, auto iter) | |||
| { | |||
| const auto isSpace = [] (juce_wchar c) { return CharacterFunctions::isWhitespace (c); }; | |||
| const auto start = [&] | |||
| { | |||
| const auto text = [&]() -> String | |||
| { | |||
| if (isForwards) | |||
| return textInterface.getText ({ currentPosition, textInterface.getTotalNumCharacters() }); | |||
| if (iter == b && includeBoundary == IncludeThisBoundary::yes) | |||
| return b; | |||
| const auto str = textInterface.getText ({ 0, currentPosition }); | |||
| const auto nudged = iter - (iter != b && includeBoundary == IncludeThisBoundary::yes ? 1 : 0); | |||
| auto start = str.getCharPointer(); | |||
| auto end = start.findTerminatingNull(); | |||
| const auto size = getAddressDifference (end.getAddress(), start.getAddress()); | |||
| return includeWhitespace == IncludeWhitespaceAfterWords::yes | |||
| ? std::find_if (nudged, e, isSpace) | |||
| : std::find_if_not (nudged, e, isSpace); | |||
| }(); | |||
| String reversed; | |||
| const auto found = includeWhitespace == IncludeWhitespaceAfterWords::yes | |||
| ? std::find_if_not (start, e, isSpace) | |||
| : std::find_if (start, e, isSpace); | |||
| if (size > 0) | |||
| { | |||
| reversed.preallocateBytes ((size_t) size); | |||
| return countDifference (iter, found); | |||
| }; | |||
| auto destPtr = reversed.getCharPointer(); | |||
| return direction == Direction::forwards ? move (begin, end, ptr) | |||
| : -move (std::make_reverse_iterator (end), | |||
| std::make_reverse_iterator (begin), | |||
| std::make_reverse_iterator (ptr)); | |||
| } | |||
| for (;;) | |||
| { | |||
| destPtr.write (*--end); | |||
| /* Returns the number of characters between ptr and the beginning of the next line in a | |||
| specific direction. | |||
| */ | |||
| template <typename CharPtr> | |||
| static int findNextLineOffset (CharPtr beginIn, | |||
| CharPtr endIn, | |||
| CharPtr ptrIn, | |||
| Direction direction, | |||
| IncludeThisBoundary includeBoundary) | |||
| { | |||
| const auto begin = makeCharPtrIteratorAdapter (beginIn); | |||
| const auto end = makeCharPtrIteratorAdapter (endIn); | |||
| const auto ptr = makeCharPtrIteratorAdapter (ptrIn); | |||
| if (end == start) | |||
| break; | |||
| } | |||
| const auto findNewline = [] (auto from, auto to) { return std::find (from, to, juce_wchar { '\n' }); }; | |||
| destPtr.writeNull(); | |||
| } | |||
| if (direction == Direction::forwards) | |||
| { | |||
| if (ptr != begin && includeBoundary == IncludeThisBoundary::yes && *(ptr - 1) == '\n') | |||
| return 0; | |||
| return reversed; | |||
| }(); | |||
| const auto newline = findNewline (ptr, end); | |||
| return countDifference (ptr, newline) + (newline == end ? 0 : 1); | |||
| } | |||
| auto tokens = (boundary == BoundaryType::line ? StringArray::fromLines (text) | |||
| : StringArray::fromTokens (text, false)); | |||
| const auto rbegin = std::make_reverse_iterator (ptr); | |||
| const auto rend = std::make_reverse_iterator (begin); | |||
| return currentPosition + offsetWithDirection (tokens[0].length()); | |||
| return -countDifference (rbegin, findNewline (rbegin + (rbegin == rend || includeBoundary == IncludeThisBoundary::yes ? 0 : 1), rend)); | |||
| } | |||
| /* Unfortunately, the method of computing end-points of text units depends on context, and on | |||
| the current platform. | |||
| Some examples of different behaviour: | |||
| - On Android, updating the cursor/selection always searches for the next text unit boundary; | |||
| but on Windows, ExpandToEnclosingUnit() should not move the starting point of the | |||
| selection if it already at a unit boundary. This means that we need both inclusive and | |||
| exclusive methods for finding the next text boundary. | |||
| - On Android, moving the cursor by 'words' should move to the first space following a | |||
| non-space character in the requested direction. On Windows, a 'word' includes trailing | |||
| whitespace, but not preceding whitespace. This means that we need a way of specifying | |||
| whether whitespace should be included when navigating by words. | |||
| */ | |||
| static int findTextBoundary (const AccessibilityTextInterface& textInterface, | |||
| int currentPosition, | |||
| BoundaryType boundary, | |||
| Direction direction, | |||
| IncludeThisBoundary includeBoundary, | |||
| IncludeWhitespaceAfterWords includeWhitespace) | |||
| { | |||
| const auto numCharacters = textInterface.getTotalNumCharacters(); | |||
| const auto isForwards = (direction == Direction::forwards); | |||
| const auto currentClamped = jlimit (0, numCharacters, currentPosition); | |||
| switch (boundary) | |||
| { | |||
| case BoundaryType::character: | |||
| { | |||
| const auto offset = includeBoundary == IncludeThisBoundary::yes ? 0 | |||
| : (isForwards ? 1 : -1); | |||
| return jlimit (0, numCharacters, currentPosition + offset); | |||
| } | |||
| case BoundaryType::word: | |||
| { | |||
| const auto str = textInterface.getText ({ 0, numCharacters }); | |||
| return currentClamped + findNextWordEndOffset (str.begin(), | |||
| str.end(), | |||
| str.begin() + currentClamped, | |||
| direction, | |||
| includeBoundary, | |||
| includeWhitespace); | |||
| } | |||
| case BoundaryType::line: | |||
| { | |||
| const auto str = textInterface.getText ({ 0, numCharacters }); | |||
| return currentClamped + findNextLineOffset (str.begin(), | |||
| str.end(), | |||
| str.begin() + currentClamped, | |||
| direction, | |||
| includeBoundary); | |||
| } | |||
| case BoundaryType::document: | |||
| @@ -105,6 +272,31 @@ namespace AccessibilityTextHelpers | |||
| jassertfalse; | |||
| return -1; | |||
| } | |||
| } | |||
| /* Adjusts the current text selection range, using an algorithm appropriate for cursor movement | |||
| on Android. | |||
| */ | |||
| static Range<int> findNewSelectionRangeAndroid (const AccessibilityTextInterface& textInterface, | |||
| BoundaryType boundaryType, | |||
| ExtendSelection extend, | |||
| Direction direction) | |||
| { | |||
| const auto oldPos = textInterface.getTextInsertionOffset(); | |||
| const auto cursorPos = findTextBoundary (textInterface, | |||
| oldPos, | |||
| boundaryType, | |||
| direction, | |||
| IncludeThisBoundary::no, | |||
| IncludeWhitespaceAfterWords::no); | |||
| if (extend == ExtendSelection::no) | |||
| return { cursorPos, cursorPos }; | |||
| const auto currentSelection = textInterface.getSelection(); | |||
| const auto start = currentSelection.getStart(); | |||
| const auto end = currentSelection.getEnd(); | |||
| return Range<int>::between (cursorPos, oldPos == start ? end : start); | |||
| } | |||
| }; | |||
| } // namespace juce | |||
| @@ -59,7 +59,10 @@ namespace juce | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (obtain, "obtain", "(I)Landroid/view/accessibility/AccessibilityEvent;") \ | |||
| METHOD (setPackageName, "setPackageName", "(Ljava/lang/CharSequence;)V") \ | |||
| METHOD (setSource, "setSource", "(Landroid/view/View;I)V") \ | |||
| METHOD (setSource, "setSource","(Landroid/view/View;I)V") \ | |||
| METHOD (setAction, "setAction", "(I)V") \ | |||
| METHOD (setFromIndex, "setFromIndex", "(I)V") \ | |||
| METHOD (setToIndex, "setToIndex", "(I)V") \ | |||
| DECLARE_JNI_CLASS (AndroidAccessibilityEvent, "android/view/accessibility/AccessibilityEvent") | |||
| #undef JNI_CLASS_MEMBERS | |||
| @@ -74,13 +77,14 @@ namespace | |||
| { | |||
| constexpr int HOST_VIEW_ID = -1; | |||
| constexpr int TYPE_VIEW_CLICKED = 0x00000001, | |||
| TYPE_VIEW_SELECTED = 0x00000004, | |||
| TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000, | |||
| TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000, | |||
| TYPE_WINDOW_CONTENT_CHANGED = 0x00000800, | |||
| TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000, | |||
| TYPE_VIEW_TEXT_CHANGED = 0x00000010; | |||
| constexpr int TYPE_VIEW_CLICKED = 0x00000001, | |||
| TYPE_VIEW_SELECTED = 0x00000004, | |||
| TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000, | |||
| TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000, | |||
| TYPE_WINDOW_CONTENT_CHANGED = 0x00000800, | |||
| TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000, | |||
| TYPE_VIEW_TEXT_CHANGED = 0x00000010, | |||
| TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000; | |||
| constexpr int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001, | |||
| CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004; | |||
| @@ -198,8 +202,6 @@ static jobject getSourceView (const AccessibilityHandler& handler) | |||
| return nullptr; | |||
| } | |||
| void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes); | |||
| //============================================================================== | |||
| class AccessibilityNativeHandle | |||
| { | |||
| @@ -377,7 +379,7 @@ public: | |||
| { | |||
| env->CallVoidMethod (info, | |||
| AndroidAccessibilityNodeInfo.setText, | |||
| javaString (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() })).get()); | |||
| javaString (textInterface->getAllText()).get()); | |||
| const auto isReadOnly = textInterface->isReadOnly(); | |||
| @@ -481,10 +483,15 @@ public: | |||
| case ACTION_CLICK: | |||
| { | |||
| // Invoking the action may delete this handler | |||
| const WeakReference<AccessibilityNativeHandle> savedHandle { this }; | |||
| if ((accessibilityHandler.getCurrentState().isCheckable() && accessibilityHandler.getActions().invoke (AccessibilityActionType::toggle)) | |||
| || accessibilityHandler.getActions().invoke (AccessibilityActionType::press)) | |||
| { | |||
| sendAccessibilityEventImpl (accessibilityHandler, TYPE_VIEW_CLICKED, 0); | |||
| if (savedHandle != nullptr) | |||
| sendAccessibilityEventImpl (accessibilityHandler, TYPE_VIEW_CLICKED, 0); | |||
| return true; | |||
| } | |||
| @@ -556,7 +563,10 @@ public: | |||
| return env->CallIntMethod (arguments, AndroidBundle.getInt, key.get()); | |||
| }; | |||
| return { getKey (selectionStartKey), getKey (selectionEndKey) }; | |||
| const auto start = getKey (selectionStartKey); | |||
| const auto end = getKey (selectionEndKey); | |||
| return Range<int>::between (start, end); | |||
| } | |||
| return {}; | |||
| @@ -636,6 +646,78 @@ public: | |||
| bool isInPopulateNodeInfo() const noexcept { return inPopulateNodeInfo; } | |||
| static bool areAnyAccessibilityClientsActive() | |||
| { | |||
| auto* env = getEnv(); | |||
| auto appContext = getAppContext(); | |||
| if (appContext.get() != nullptr) | |||
| { | |||
| LocalRef<jobject> accessibilityManager (env->CallObjectMethod (appContext.get(), AndroidContext.getSystemService, | |||
| javaString ("accessibility").get())); | |||
| if (accessibilityManager != nullptr) | |||
| return env->CallBooleanMethod (accessibilityManager.get(), AndroidAccessibilityManager.isEnabled); | |||
| } | |||
| return false; | |||
| } | |||
| template <typename ModificationCallback> | |||
| static void sendAccessibilityEventExtendedImpl (const AccessibilityHandler& handler, | |||
| int eventType, | |||
| ModificationCallback&& modificationCallback) | |||
| { | |||
| if (! areAnyAccessibilityClientsActive()) | |||
| return; | |||
| if (const auto sourceView = getSourceView (handler)) | |||
| { | |||
| const auto* nativeImpl = handler.getNativeImplementation(); | |||
| if (nativeImpl == nullptr || nativeImpl->isInPopulateNodeInfo()) | |||
| return; | |||
| auto* env = getEnv(); | |||
| auto appContext = getAppContext(); | |||
| if (appContext.get() == nullptr) | |||
| return; | |||
| LocalRef<jobject> event (env->CallStaticObjectMethod (AndroidAccessibilityEvent, | |||
| AndroidAccessibilityEvent.obtain, | |||
| eventType)); | |||
| env->CallVoidMethod (event, | |||
| AndroidAccessibilityEvent.setPackageName, | |||
| env->CallObjectMethod (appContext.get(), | |||
| AndroidContext.getPackageName)); | |||
| env->CallVoidMethod (event, | |||
| AndroidAccessibilityEvent.setSource, | |||
| sourceView, | |||
| nativeImpl->getVirtualViewId()); | |||
| modificationCallback (event); | |||
| env->CallBooleanMethod (sourceView, | |||
| AndroidViewGroup.requestSendAccessibilityEvent, | |||
| sourceView, | |||
| event.get()); | |||
| } | |||
| } | |||
| static void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes) | |||
| { | |||
| sendAccessibilityEventExtendedImpl (handler, eventType, [contentChangeTypes] (auto event) | |||
| { | |||
| if (contentChangeTypes != 0 && accessibilityEventSetContentChangeTypes != nullptr) | |||
| getEnv()->CallVoidMethod (event, | |||
| accessibilityEventSetContentChangeTypes, | |||
| contentChangeTypes); | |||
| }); | |||
| } | |||
| private: | |||
| static std::unordered_map<int, AccessibilityHandler*> virtualViewIdMap; | |||
| @@ -654,7 +736,7 @@ private: | |||
| const auto valueString = [this]() -> String | |||
| { | |||
| if (auto* textInterface = accessibilityHandler.getTextInterface()) | |||
| return textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }); | |||
| return textInterface->getAllText(); | |||
| if (auto* valueInterface = accessibilityHandler.getValueInterface()) | |||
| return valueInterface->getCurrentValueAsString(); | |||
| @@ -674,66 +756,68 @@ private: | |||
| bool moveCursor (jobject arguments, bool forwards) | |||
| { | |||
| if (auto* textInterface = accessibilityHandler.getTextInterface()) | |||
| { | |||
| const auto granularityKey = javaString ("ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"); | |||
| const auto extendSelectionKey = javaString ("ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN"); | |||
| using ATH = AccessibilityTextHelpers; | |||
| auto* env = getEnv(); | |||
| auto* textInterface = accessibilityHandler.getTextInterface(); | |||
| const auto boundaryType = [&] | |||
| { | |||
| const auto granularity = env->CallIntMethod (arguments, | |||
| AndroidBundle.getInt, | |||
| granularityKey.get()); | |||
| if (textInterface == nullptr) | |||
| return false; | |||
| using BoundaryType = AccessibilityTextHelpers::BoundaryType; | |||
| const auto granularityKey = javaString ("ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"); | |||
| const auto extendSelectionKey = javaString ("ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN"); | |||
| switch (granularity) | |||
| { | |||
| case MOVEMENT_GRANULARITY_CHARACTER: return BoundaryType::character; | |||
| case MOVEMENT_GRANULARITY_WORD: return BoundaryType::word; | |||
| case MOVEMENT_GRANULARITY_LINE: return BoundaryType::line; | |||
| case MOVEMENT_GRANULARITY_PARAGRAPH: | |||
| case MOVEMENT_GRANULARITY_PAGE: return BoundaryType::document; | |||
| } | |||
| jassertfalse; | |||
| return BoundaryType::character; | |||
| }(); | |||
| auto* env = getEnv(); | |||
| using Direction = AccessibilityTextHelpers::Direction; | |||
| const auto boundaryType = [&] | |||
| { | |||
| const auto granularity = env->CallIntMethod (arguments, AndroidBundle.getInt, granularityKey.get()); | |||
| const auto cursorPos = AccessibilityTextHelpers::findTextBoundary (*textInterface, | |||
| textInterface->getTextInsertionOffset(), | |||
| boundaryType, | |||
| forwards ? Direction::forwards | |||
| : Direction::backwards); | |||
| using BoundaryType = ATH::BoundaryType; | |||
| const auto newSelection = [&]() -> Range<int> | |||
| switch (granularity) | |||
| { | |||
| const auto currentSelection = textInterface->getSelection(); | |||
| const auto extendSelection = env->CallBooleanMethod (arguments, | |||
| AndroidBundle.getBoolean, | |||
| extendSelectionKey.get()); | |||
| case MOVEMENT_GRANULARITY_CHARACTER: return BoundaryType::character; | |||
| case MOVEMENT_GRANULARITY_WORD: return BoundaryType::word; | |||
| case MOVEMENT_GRANULARITY_LINE: return BoundaryType::line; | |||
| case MOVEMENT_GRANULARITY_PARAGRAPH: | |||
| case MOVEMENT_GRANULARITY_PAGE: return BoundaryType::document; | |||
| } | |||
| if (! extendSelection) | |||
| return { cursorPos, cursorPos }; | |||
| jassertfalse; | |||
| return BoundaryType::character; | |||
| }(); | |||
| const auto start = currentSelection.getStart(); | |||
| const auto end = currentSelection.getEnd(); | |||
| const auto direction = forwards | |||
| ? ATH::Direction::forwards | |||
| : ATH::Direction::backwards; | |||
| if (forwards) | |||
| return { start, jmax (start, cursorPos) }; | |||
| const auto extend = env->CallBooleanMethod (arguments, AndroidBundle.getBoolean, extendSelectionKey.get()) | |||
| ? ATH::ExtendSelection::yes | |||
| : ATH::ExtendSelection::no; | |||
| return { jmin (start, cursorPos), end }; | |||
| }(); | |||
| const auto oldSelection = textInterface->getSelection(); | |||
| const auto newSelection = ATH::findNewSelectionRangeAndroid (*textInterface, boundaryType, extend, direction); | |||
| textInterface->setSelection (newSelection); | |||
| textInterface->setSelection (newSelection); | |||
| return true; | |||
| } | |||
| // Required for Android to read back the text that the cursor moved over | |||
| sendAccessibilityEventExtendedImpl (accessibilityHandler, TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, [&] (auto event) | |||
| { | |||
| env->CallVoidMethod (event, | |||
| AndroidAccessibilityEvent.setAction, | |||
| forwards ? ACTION_NEXT_AT_MOVEMENT_GRANULARITY : ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); | |||
| return false; | |||
| env->CallVoidMethod (event, | |||
| AndroidAccessibilityEvent.setFromIndex, | |||
| oldSelection.getStart() != newSelection.getStart() ? oldSelection.getStart() | |||
| : oldSelection.getEnd()); | |||
| env->CallVoidMethod (event, | |||
| AndroidAccessibilityEvent.setToIndex, | |||
| oldSelection.getStart() != newSelection.getStart() ? newSelection.getStart() | |||
| : newSelection.getEnd()); | |||
| }); | |||
| return true; | |||
| } | |||
| AccessibilityHandler& accessibilityHandler; | |||
| @@ -742,6 +826,8 @@ private: | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeHandle) | |||
| JUCE_DECLARE_WEAK_REFERENCEABLE (AccessibilityNativeHandle) | |||
| }; | |||
| std::unordered_map<int, AccessibilityHandler*> AccessibilityNativeHandle::virtualViewIdMap; | |||
| @@ -758,67 +844,6 @@ AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const | |||
| return nativeImpl.get(); | |||
| } | |||
| static bool areAnyAccessibilityClientsActive() | |||
| { | |||
| auto* env = getEnv(); | |||
| auto appContext = getAppContext(); | |||
| if (appContext.get() != nullptr) | |||
| { | |||
| LocalRef<jobject> accessibilityManager (env->CallObjectMethod (appContext.get(), AndroidContext.getSystemService, | |||
| javaString ("accessibility").get())); | |||
| if (accessibilityManager != nullptr) | |||
| return env->CallBooleanMethod (accessibilityManager.get(), AndroidAccessibilityManager.isEnabled); | |||
| } | |||
| return false; | |||
| } | |||
| void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes) | |||
| { | |||
| if (! areAnyAccessibilityClientsActive()) | |||
| return; | |||
| if (const auto sourceView = getSourceView (handler)) | |||
| { | |||
| const auto* nativeImpl = handler.getNativeImplementation(); | |||
| if (nativeImpl == nullptr || nativeImpl->isInPopulateNodeInfo()) | |||
| return; | |||
| auto* env = getEnv(); | |||
| auto appContext = getAppContext(); | |||
| if (appContext.get() == nullptr) | |||
| return; | |||
| LocalRef<jobject> event (env->CallStaticObjectMethod (AndroidAccessibilityEvent, | |||
| AndroidAccessibilityEvent.obtain, | |||
| eventType)); | |||
| env->CallVoidMethod (event, | |||
| AndroidAccessibilityEvent.setPackageName, | |||
| env->CallObjectMethod (appContext.get(), | |||
| AndroidContext.getPackageName)); | |||
| env->CallVoidMethod (event, | |||
| AndroidAccessibilityEvent.setSource, | |||
| sourceView, | |||
| nativeImpl->getVirtualViewId()); | |||
| if (contentChangeTypes != 0 && accessibilityEventSetContentChangeTypes != nullptr) | |||
| env->CallVoidMethod (event, | |||
| accessibilityEventSetContentChangeTypes, | |||
| contentChangeTypes); | |||
| env->CallBooleanMethod (sourceView, | |||
| AndroidViewGroup.requestSendAccessibilityEvent, | |||
| sourceView, | |||
| event.get()); | |||
| } | |||
| } | |||
| void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, | |||
| InternalAccessibilityEvent eventType) | |||
| { | |||
| @@ -827,7 +852,7 @@ void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, | |||
| || eventType == InternalAccessibilityEvent::elementMovedOrResized) | |||
| { | |||
| if (auto* parent = handler.getParent()) | |||
| sendAccessibilityEventImpl (*parent, TYPE_WINDOW_CONTENT_CHANGED, CONTENT_CHANGE_TYPE_SUBTREE); | |||
| AccessibilityNativeHandle::sendAccessibilityEventImpl (*parent, TYPE_WINDOW_CONTENT_CHANGED, CONTENT_CHANGE_TYPE_SUBTREE); | |||
| return; | |||
| } | |||
| @@ -852,7 +877,7 @@ void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, | |||
| }(); | |||
| if (notification != 0) | |||
| sendAccessibilityEventImpl (handler, notification, 0); | |||
| AccessibilityNativeHandle::sendAccessibilityEventImpl (handler, notification, 0); | |||
| } | |||
| void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const | |||
| @@ -885,13 +910,13 @@ void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventTyp | |||
| return 0; | |||
| }(); | |||
| sendAccessibilityEventImpl (*this, notification, contentChangeTypes); | |||
| AccessibilityNativeHandle::sendAccessibilityEventImpl (*this, notification, contentChangeTypes); | |||
| } | |||
| void AccessibilityHandler::postAnnouncement (const String& announcementString, | |||
| AnnouncementPriority) | |||
| { | |||
| if (! areAnyAccessibilityClientsActive()) | |||
| if (! AccessibilityNativeHandle::areAnyAccessibilityClientsActive()) | |||
| return; | |||
| const auto rootView = [] | |||
| @@ -206,10 +206,14 @@ void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventTyp | |||
| { | |||
| if (auto* valueInterface = getValueInterface()) | |||
| { | |||
| VARIANT newValue; | |||
| VariantHelpers::setString (valueInterface->getCurrentValueAsString(), &newValue); | |||
| const auto propertyType = getRole() == AccessibilityRole::slider ? UIA_RangeValueValuePropertyId | |||
| : UIA_ValueValuePropertyId; | |||
| sendAccessibilityPropertyChangedEvent (*this, UIA_ValueValuePropertyId, newValue); | |||
| const auto value = getRole() == AccessibilityRole::slider | |||
| ? VariantHelpers::getWithValue (valueInterface->getCurrentValue()) | |||
| : VariantHelpers::getWithValue (valueInterface->getCurrentValueAsString()); | |||
| sendAccessibilityPropertyChangedEvent (*this, propertyType, value); | |||
| } | |||
| return; | |||
| @@ -28,6 +28,17 @@ namespace juce | |||
| namespace VariantHelpers | |||
| { | |||
| namespace Detail | |||
| { | |||
| template <typename Fn, typename ValueType> | |||
| inline VARIANT getWithValueGeneric (Fn&& setter, ValueType value) | |||
| { | |||
| VARIANT result{}; | |||
| setter (value, &result); | |||
| return result; | |||
| } | |||
| } | |||
| inline void clear (VARIANT* variant) | |||
| { | |||
| variant->vt = VT_EMPTY; | |||
| @@ -56,6 +67,9 @@ namespace VariantHelpers | |||
| variant->vt = VT_R8; | |||
| variant->dblVal = value; | |||
| } | |||
| inline VARIANT getWithValue (double value) { return Detail::getWithValueGeneric (&setDouble, value); } | |||
| inline VARIANT getWithValue (const String& value) { return Detail::getWithValueGeneric (&setString, value); } | |||
| } | |||
| inline JUCE_COMRESULT addHandlersToArray (const std::vector<const AccessibilityHandler*>& handlers, SAFEARRAY** pRetVal) | |||
| @@ -246,19 +246,22 @@ private: | |||
| if (auto* textInterface = owner->getHandler().getTextInterface()) | |||
| { | |||
| const auto boundaryType = getBoundaryType (unit); | |||
| const auto start = unit == ComTypes::TextUnit::TextUnit_Character | |||
| ? selectionRange.getStart() | |||
| : AccessibilityTextHelpers::findTextBoundary (*textInterface, | |||
| selectionRange.getStart(), | |||
| boundaryType, | |||
| AccessibilityTextHelpers::Direction::backwards); | |||
| using ATH = AccessibilityTextHelpers; | |||
| const auto end = AccessibilityTextHelpers::findTextBoundary (*textInterface, | |||
| start, | |||
| boundaryType, | |||
| AccessibilityTextHelpers::Direction::forwards); | |||
| const auto boundaryType = getBoundaryType (unit); | |||
| const auto start = ATH::findTextBoundary (*textInterface, | |||
| selectionRange.getStart(), | |||
| boundaryType, | |||
| ATH::Direction::backwards, | |||
| ATH::IncludeThisBoundary::yes, | |||
| ATH::IncludeWhitespaceAfterWords::no); | |||
| const auto end = ATH::findTextBoundary (*textInterface, | |||
| start, | |||
| boundaryType, | |||
| ATH::Direction::forwards, | |||
| ATH::IncludeThisBoundary::no, | |||
| ATH::IncludeWhitespaceAfterWords::yes); | |||
| selectionRange = Range<int> (start, end); | |||
| @@ -413,19 +416,39 @@ private: | |||
| JUCE_COMRESULT Move (ComTypes::TextUnit unit, int count, int* pRetVal) override | |||
| { | |||
| return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface&) | |||
| return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||
| { | |||
| if (count > 0) | |||
| { | |||
| MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_End, unit, count, pRetVal); | |||
| MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_Start, unit, count, pRetVal); | |||
| } | |||
| else if (count < 0) | |||
| using ATH = AccessibilityTextHelpers; | |||
| const auto boundaryType = getBoundaryType (unit); | |||
| const auto previousUnitBoundary = ATH::findTextBoundary (textInterface, | |||
| selectionRange.getStart(), | |||
| boundaryType, | |||
| ATH::Direction::backwards, | |||
| ATH::IncludeThisBoundary::yes, | |||
| ATH::IncludeWhitespaceAfterWords::no); | |||
| auto numMoved = 0; | |||
| auto movedEndpoint = previousUnitBoundary; | |||
| for (; numMoved < std::abs (count); ++numMoved) | |||
| { | |||
| MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_Start, unit, count, pRetVal); | |||
| MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_End, unit, count, pRetVal); | |||
| const auto nextEndpoint = ATH::findTextBoundary (textInterface, | |||
| movedEndpoint, | |||
| boundaryType, | |||
| count > 0 ? ATH::Direction::forwards : ATH::Direction::backwards, | |||
| ATH::IncludeThisBoundary::no, | |||
| count > 0 ? ATH::IncludeWhitespaceAfterWords::yes : ATH::IncludeWhitespaceAfterWords::no); | |||
| if (nextEndpoint == movedEndpoint) | |||
| break; | |||
| movedEndpoint = nextEndpoint; | |||
| } | |||
| *pRetVal = numMoved; | |||
| ExpandToEnclosingUnit (unit); | |||
| return S_OK; | |||
| }); | |||
| } | |||
| @@ -463,34 +486,37 @@ private: | |||
| if (count == 0 || textInterface.getTotalNumCharacters() == 0) | |||
| return S_OK; | |||
| auto endpointToMove = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart() | |||
| : selectionRange.getEnd()); | |||
| const auto endpointToMove = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart() | |||
| : selectionRange.getEnd()); | |||
| const auto direction = (count > 0 ? AccessibilityTextHelpers::Direction::forwards | |||
| : AccessibilityTextHelpers::Direction::backwards); | |||
| using ATH = AccessibilityTextHelpers; | |||
| const auto boundaryType = getBoundaryType (unit); | |||
| const auto direction = (count > 0 ? ATH::Direction::forwards | |||
| : ATH::Direction::backwards); | |||
| // handle case where endpoint is on a boundary | |||
| if (AccessibilityTextHelpers::findTextBoundary (textInterface, endpointToMove, boundaryType, direction) == endpointToMove) | |||
| endpointToMove += (direction == AccessibilityTextHelpers::Direction::forwards ? 1 : -1); | |||
| const auto boundaryType = getBoundaryType (unit); | |||
| auto movedEndpoint = endpointToMove; | |||
| int numMoved; | |||
| for (numMoved = 0; numMoved < std::abs (count); ++numMoved) | |||
| int numMoved = 0; | |||
| for (; numMoved < std::abs (count); ++numMoved) | |||
| { | |||
| auto nextEndpoint = AccessibilityTextHelpers::findTextBoundary (textInterface, | |||
| endpointToMove, | |||
| boundaryType, | |||
| direction); | |||
| if (nextEndpoint == endpointToMove) | |||
| auto nextEndpoint = ATH::findTextBoundary (textInterface, | |||
| movedEndpoint, | |||
| boundaryType, | |||
| direction, | |||
| ATH::IncludeThisBoundary::no, | |||
| direction == ATH::Direction::forwards ? ATH::IncludeWhitespaceAfterWords::yes | |||
| : ATH::IncludeWhitespaceAfterWords::no); | |||
| if (nextEndpoint == movedEndpoint) | |||
| break; | |||
| endpointToMove = nextEndpoint; | |||
| movedEndpoint = nextEndpoint; | |||
| } | |||
| *pRetVal = numMoved; | |||
| setEndpointChecked (endpoint, endpointToMove); | |||
| setEndpointChecked (endpoint, movedEndpoint); | |||
| return S_OK; | |||
| }); | |||
| @@ -1,900 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 7 End-User License | |||
| Agreement and JUCE Privacy Policy. | |||
| End User License Agreement: www.juce.com/juce-7-licence | |||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| // This byte-code is generated from native/java/app/com/rmsl/juce/JuceContentProviderCursor.java with min sdk version 16 | |||
| // See juce_core/native/java/README.txt on how to generate this byte-code. | |||
| static const uint8 javaJuceContentProviderCursor[] = | |||
| {31,139,8,8,191,114,161,94,0,3,106,97,118,97,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,67,117, | |||
| 114,115,111,114,46,100,101,120,0,117,147,177,111,211,64,20,198,223,157,157,148,150,54,164,192,208,14,64,144,16,18,67,235,138,2, | |||
| 75,40,162,10,44,150,65,149,2,25,218,233,176,173,198,37,241,69,182,19,121,96,160,21,136,37,19,98,234,80,85,149,152,88,24,248, | |||
| 3,24,146,63,130,141,137,129,13,169,99,7,190,203,157,33,18,194,210,207,247,222,229,189,239,157,206,95,130,48,159,91,91,191,75,227, | |||
| 60,200,143,134,239,247,151,62,189,43,175,127,249,246,235,241,215,241,112,231,231,193,237,135,22,81,143,136,242,214,157,139, | |||
| 100,158,99,78,84,37,189,95,2,159,129,13,70,128,129,83,179,127,102,242,27,120,157,129,71,224,16,156,128,143,96,12,126,128,69,232, | |||
| 93,6,75,224,10,184,14,238,129,13,224,130,16,188,4,3,174,245,44,51,79,205,152,53,171,101,206,86,54,241,27,20,206,152,120,136, | |||
| 248,156,137,63,32,134,12,45,76,206,166,187,148,230,28,169,125,62,201,249,159,156,209,188,201,23,77,93,241,187,122,134,38,40,225, | |||
| 52,42,124,197,245,252,94,141,104,147,182,113,95,21,76,208,83,222,114,125,86,89,101,168,109,162,162,183,134,46,86,249,71,215, | |||
| 158,228,54,149,239,71,113,148,61,32,230,210,85,183,239,135,13,25,103,97,156,109,37,114,16,5,97,210,232,39,169,76,86,247,196,64, | |||
| 208,53,79,196,65,34,163,192,9,68,38,94,136,52,116,158,136,44,137,114,93,84,167,91,158,47,187,78,210,77,59,206,30,164,156,255, | |||
| 234,213,137,181,136,183,92,178,90,174,135,192,163,75,59,158,154,225,116,68,188,235,52,33,26,239,214,169,228,119,100,26,210,121, | |||
| 95,118,250,221,248,169,232,134,41,45,251,90,176,217,22,73,33,215,80,101,1,217,109,153,102,52,171,222,207,228,115,52,218,89, | |||
| 59,74,233,38,191,48,63,83,217,88,161,85,194,178,141,139,224,184,28,190,255,218,30,113,126,192,201,98,223,249,130,185,27,54,181, | |||
| 22,222,227,83,254,43,60,49,50,235,180,15,11,47,150,167,252,200,106,186,95,121,146,85,255,122,134,215,180,190,242,169,101,106, | |||
| 212,119,165,154,238,157,124,243,170,142,213,255,224,55,143,234,50,200,64,3,0,0,0,0}; | |||
| // This byte-code is generated from native/java/app/com/rmsl/juce/JuceContentProviderFileObserver.java with min sdk version 16 | |||
| // See juce_core/native/java/README.txt on how to generate this byte-code. | |||
| static const uint8 javaJuceContentProviderFileObserver[] = | |||
| {31,139,8,8,194,122,161,94,0,3,106,97,118,97,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,70,105, | |||
| 108,101,79,98,115,101,114,118,101,114,46,100,101,120,0,133,147,205,107,19,65,24,198,223,249,72,98,171,46,105,235,69,16,201,65,81, | |||
| 68,221,136,10,66,84,144,250,65,194,130,197,212,32,5,15,155,100,104,182,38,187,97,119,141,241,32,126,30,196,147,23,79,246,216, | |||
| 131,120,202,77,169,80,212,191,64,193,66,143,30,60,138,255,130,62,179,51,165,219,147,129,223,188,239,188,239,204,179,179,179,79, | |||
| 186,106,60,93,61,123,158,54,159,255,248,112,97,210,120,124,98,237,251,177,7,109,245,115,253,225,198,159,47,243,171,135,198,130, | |||
| 104,72,68,227,214,185,89,178,191,45,78,116,128,76,189,8,62,3,169,235,128,129,61,204,204,203,204,204,171,24,142,99,207,2,226, | |||
| 4,124,4,159,192,6,248,5,254,130,42,250,87,193,13,224,129,91,224,14,184,11,30,129,23,224,21,120,3,222,130,53,240,158,27,125,110, | |||
| 159,95,176,231,41,233,51,216,249,75,44,152,178,249,107,228,211,54,95,69,190,215,230,239,144,11,40,57,153,150,200,222,81,100, | |||
| 170,166,190,47,139,68,51,185,200,237,93,8,27,191,218,66,17,138,186,54,225,230,44,195,42,209,149,194,18,238,206,201,58,250,121, | |||
| 235,182,215,172,160,191,200,137,159,113,172,158,204,246,50,251,62,38,151,89,103,251,29,139,23,131,48,72,47,19,171,19,107,208, | |||
| 145,198,253,142,154,143,194,84,133,233,66,28,141,130,174,138,175,7,125,117,179,157,168,120,164,226,211,43,254,200,167,131,158, | |||
| 31,118,227,40,232,186,81,226,230,219,53,114,189,78,52,112,227,65,210,119,87,32,229,254,71,175,70,179,158,150,116,251,126,184, | |||
| 236,54,211,56,8,151,107,196,90,36,90,117,143,100,171,97,70,175,142,2,134,195,29,35,213,236,249,241,110,161,107,35,148,169,160, | |||
| 178,32,123,81,146,210,148,30,23,163,219,137,34,57,240,147,123,84,138,66,179,76,14,253,180,71,50,237,5,9,29,21,229,185,153,146, | |||
| 115,233,20,157,228,206,92,201,89,194,21,113,70,156,61,125,34,191,113,246,12,223,143,253,198,101,237,183,223,133,229,226,182,103, | |||
| 121,206,183,34,231,93,153,243,111,129,118,60,92,164,29,31,179,138,217,175,189,204,202,102,141,246,24,175,24,125,237,111,97, | |||
| 215,104,15,80,197,236,205,252,81,54,185,254,255,252,3,243,31,208,130,120,3,0,0,0,0}; | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| FIELD (authority, "authority", "Ljava/lang/String;") | |||
| DECLARE_JNI_CLASS (AndroidProviderInfo, "android/content/pm/ProviderInfo") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (constructor, "<init>", "(Landroid/os/ParcelFileDescriptor;JJ)V") \ | |||
| METHOD (createInputStream, "createInputStream", "()Ljava/io/FileInputStream;") \ | |||
| METHOD (getLength, "getLength", "()J") | |||
| DECLARE_JNI_CLASS (AssetFileDescriptor, "android/content/res/AssetFileDescriptor") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (close, "close", "()V") | |||
| DECLARE_JNI_CLASS (JavaCloseable, "java/io/Closeable") | |||
| #undef JNI_CLASS_MEMBERS | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| STATICMETHOD (open, "open", "(Ljava/io/File;I)Landroid/os/ParcelFileDescriptor;") | |||
| DECLARE_JNI_CLASS (ParcelFileDescriptor, "android/os/ParcelFileDescriptor") | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| class AndroidContentSharerCursor | |||
| { | |||
| public: | |||
| class Owner | |||
| { | |||
| public: | |||
| virtual ~Owner() {} | |||
| virtual void cursorClosed (const AndroidContentSharerCursor&) = 0; | |||
| }; | |||
| AndroidContentSharerCursor (Owner& ownerToUse, JNIEnv* env, | |||
| const LocalRef<jobject>& contentProvider, | |||
| const LocalRef<jobjectArray>& resultColumns) | |||
| : owner (ownerToUse), | |||
| cursor (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderCursor, | |||
| JuceContentProviderCursor.constructor, | |||
| reinterpret_cast<jlong> (this), | |||
| resultColumns.get())))) | |||
| { | |||
| // the content provider must be created first | |||
| jassert (contentProvider.get() != nullptr); | |||
| } | |||
| jobject getNativeCursor() { return cursor.get(); } | |||
| void cursorClosed() | |||
| { | |||
| MessageManager::callAsync ([this] { owner.cursorClosed (*this); }); | |||
| } | |||
| void addRow (LocalRef<jobjectArray>& values) | |||
| { | |||
| auto* env = getEnv(); | |||
| env->CallVoidMethod (cursor.get(), JuceContentProviderCursor.addRow, values.get()); | |||
| } | |||
| private: | |||
| Owner& owner; | |||
| GlobalRef cursor; | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (addRow, "addRow", "([Ljava/lang/Object;)V") \ | |||
| METHOD (constructor, "<init>", "(J[Ljava/lang/String;)V") \ | |||
| CALLBACK (contentSharerCursorClosed, "contentSharerCursorClosed", "(J)V") \ | |||
| DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderCursor, "com/rmsl/juce/JuceContentProviderCursor", 16, javaJuceContentProviderCursor, sizeof (javaJuceContentProviderCursor)) | |||
| #undef JNI_CLASS_MEMBERS | |||
| static void JNICALL contentSharerCursorClosed (JNIEnv*, jobject, jlong host) | |||
| { | |||
| if (auto* myself = reinterpret_cast<AndroidContentSharerCursor*> (host)) | |||
| myself->cursorClosed(); | |||
| } | |||
| }; | |||
| AndroidContentSharerCursor::JuceContentProviderCursor_Class AndroidContentSharerCursor::JuceContentProviderCursor; | |||
| //============================================================================== | |||
| class AndroidContentSharerFileObserver | |||
| { | |||
| public: | |||
| class Owner | |||
| { | |||
| public: | |||
| virtual ~Owner() {} | |||
| virtual void fileHandleClosed (const AndroidContentSharerFileObserver&) = 0; | |||
| }; | |||
| AndroidContentSharerFileObserver (Owner& ownerToUse, JNIEnv* env, | |||
| const LocalRef<jobject>& contentProvider, | |||
| const String& filepathToUse) | |||
| : owner (ownerToUse), | |||
| filepath (filepathToUse), | |||
| fileObserver (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderFileObserver, | |||
| JuceContentProviderFileObserver.constructor, | |||
| reinterpret_cast<jlong> (this), | |||
| javaString (filepath).get(), | |||
| open | access | closeWrite | closeNoWrite)))) | |||
| { | |||
| // the content provider must be created first | |||
| jassert (contentProvider.get() != nullptr); | |||
| env->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.startWatching); | |||
| } | |||
| void onFileEvent (int event, const LocalRef<jstring>& path) | |||
| { | |||
| ignoreUnused (path); | |||
| if (event == open) | |||
| { | |||
| ++numOpenedHandles; | |||
| } | |||
| else if (event == access) | |||
| { | |||
| fileWasRead = true; | |||
| } | |||
| else if (event == closeNoWrite || event == closeWrite) | |||
| { | |||
| --numOpenedHandles; | |||
| // numOpenedHandles may get negative if we don't receive open handle event. | |||
| if (fileWasRead && numOpenedHandles <= 0) | |||
| { | |||
| MessageManager::callAsync ([this] | |||
| { | |||
| getEnv()->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.stopWatching); | |||
| owner.fileHandleClosed (*this); | |||
| }); | |||
| } | |||
| } | |||
| } | |||
| private: | |||
| static constexpr int open = 32; | |||
| static constexpr int access = 1; | |||
| static constexpr int closeWrite = 8; | |||
| static constexpr int closeNoWrite = 16; | |||
| bool fileWasRead = false; | |||
| int numOpenedHandles = 0; | |||
| Owner& owner; | |||
| String filepath; | |||
| GlobalRef fileObserver; | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| METHOD (constructor, "<init>", "(JLjava/lang/String;I)V") \ | |||
| METHOD (startWatching, "startWatching", "()V") \ | |||
| METHOD (stopWatching, "stopWatching", "()V") \ | |||
| CALLBACK (contentSharerFileObserverEvent, "contentSharerFileObserverEvent", "(JILjava/lang/String;)V") \ | |||
| DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderFileObserver, "com/rmsl/juce/JuceContentProviderFileObserver", 16, javaJuceContentProviderFileObserver, sizeof (javaJuceContentProviderFileObserver)) | |||
| #undef JNI_CLASS_MEMBERS | |||
| static void JNICALL contentSharerFileObserverEvent (JNIEnv*, jobject /*fileObserver*/, jlong host, int event, jstring path) | |||
| { | |||
| if (auto* myself = reinterpret_cast<AndroidContentSharerFileObserver*> (host)) | |||
| myself->onFileEvent (event, LocalRef<jstring> (path)); | |||
| } | |||
| }; | |||
| AndroidContentSharerFileObserver::JuceContentProviderFileObserver_Class AndroidContentSharerFileObserver::JuceContentProviderFileObserver; | |||
| //============================================================================== | |||
| class AndroidContentSharerPrepareFilesThread : private Thread | |||
| { | |||
| public: | |||
| AndroidContentSharerPrepareFilesThread (AsyncUpdater& ownerToUse, | |||
| const Array<URL>& fileUrlsToUse, | |||
| const String& packageNameToUse, | |||
| const String& uriBaseToUse) | |||
| : Thread ("AndroidContentSharerPrepareFilesThread"), | |||
| owner (ownerToUse), | |||
| fileUrls (fileUrlsToUse), | |||
| resultFileUris (GlobalRef (LocalRef<jobject> (getEnv()->NewObject (JavaArrayList, | |||
| JavaArrayList.constructor, | |||
| fileUrls.size())))), | |||
| packageName (packageNameToUse), | |||
| uriBase (uriBaseToUse) | |||
| { | |||
| startThread(); | |||
| } | |||
| ~AndroidContentSharerPrepareFilesThread() override | |||
| { | |||
| signalThreadShouldExit(); | |||
| waitForThreadToExit (10000); | |||
| for (auto& f : temporaryFilesFromAssetFiles) | |||
| f.deleteFile(); | |||
| } | |||
| jobject getResultFileUris() { return resultFileUris.get(); } | |||
| const StringArray& getMimeTypes() const { return mimeTypes; } | |||
| const StringArray& getFilePaths() const { return filePaths; } | |||
| private: | |||
| struct StreamCloser | |||
| { | |||
| StreamCloser (const LocalRef<jobject>& streamToUse) | |||
| : stream (GlobalRef (streamToUse)) | |||
| { | |||
| } | |||
| ~StreamCloser() | |||
| { | |||
| if (stream.get() != nullptr) | |||
| getEnv()->CallVoidMethod (stream, JavaCloseable.close); | |||
| } | |||
| GlobalRef stream; | |||
| }; | |||
| void run() override | |||
| { | |||
| auto* env = getEnv(); | |||
| bool canSpecifyMimeTypes = true; | |||
| for (auto f : fileUrls) | |||
| { | |||
| auto scheme = f.getScheme(); | |||
| // Only "file://" scheme or no scheme (for files in app bundle) are allowed! | |||
| jassert (scheme.isEmpty() || scheme == "file"); | |||
| if (scheme.isEmpty()) | |||
| { | |||
| // Raw resource names need to be all lower case | |||
| jassert (f.toString (true).toLowerCase() == f.toString (true)); | |||
| // This will get us a file with file:// URI | |||
| f = copyAssetFileToTemporaryFile (env, f.toString (true)); | |||
| if (f.isEmpty()) | |||
| continue; | |||
| } | |||
| if (threadShouldExit()) | |||
| return; | |||
| auto filepath = URL::removeEscapeChars (f.toString (true).fromFirstOccurrenceOf ("file://", false, false)); | |||
| filePaths.add (filepath); | |||
| auto filename = filepath.fromLastOccurrenceOf ("/", false, true); | |||
| auto fileExtension = filename.fromLastOccurrenceOf (".", false, true); | |||
| auto contentString = uriBase + String (filePaths.size() - 1) + "/" + filename; | |||
| auto uri = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, | |||
| javaString (contentString).get())); | |||
| if (canSpecifyMimeTypes) | |||
| canSpecifyMimeTypes = fileExtension.isNotEmpty(); | |||
| if (canSpecifyMimeTypes) | |||
| mimeTypes.addArray (getMimeTypesForFileExtension (fileExtension)); | |||
| else | |||
| mimeTypes.clear(); | |||
| env->CallBooleanMethod (resultFileUris, JavaArrayList.add, uri.get()); | |||
| } | |||
| owner.triggerAsyncUpdate(); | |||
| } | |||
| URL copyAssetFileToTemporaryFile (JNIEnv* env, const String& filename) | |||
| { | |||
| auto resources = LocalRef<jobject> (env->CallObjectMethod (getAppContext().get(), AndroidContext.getResources)); | |||
| int fileId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (filename).get(), | |||
| javaString ("raw").get(), javaString (packageName).get()); | |||
| // Raw resource not found. Please make sure that you include your file as a raw resource | |||
| // and that you specify just the file name, without an extension. | |||
| jassert (fileId != 0); | |||
| if (fileId == 0) | |||
| return {}; | |||
| auto assetFd = LocalRef<jobject> (env->CallObjectMethod (resources, | |||
| AndroidResources.openRawResourceFd, | |||
| fileId)); | |||
| auto inputStream = StreamCloser (LocalRef<jobject> (env->CallObjectMethod (assetFd, | |||
| AssetFileDescriptor.createInputStream))); | |||
| if (jniCheckHasExceptionOccurredAndClear()) | |||
| { | |||
| // Failed to open file stream for resource | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| auto tempFile = File::createTempFile ({}); | |||
| tempFile.createDirectory(); | |||
| tempFile = tempFile.getChildFile (filename); | |||
| auto outputStream = StreamCloser (LocalRef<jobject> (env->NewObject (JavaFileOutputStream, | |||
| JavaFileOutputStream.constructor, | |||
| javaString (tempFile.getFullPathName()).get()))); | |||
| if (jniCheckHasExceptionOccurredAndClear()) | |||
| { | |||
| // Failed to open file stream for temporary file | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| auto buffer = LocalRef<jbyteArray> (env->NewByteArray (1024)); | |||
| int bytesRead = 0; | |||
| for (;;) | |||
| { | |||
| if (threadShouldExit()) | |||
| return {}; | |||
| bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get()); | |||
| if (jniCheckHasExceptionOccurredAndClear()) | |||
| { | |||
| // Failed to read from resource file. | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| if (bytesRead < 0) | |||
| break; | |||
| env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead); | |||
| if (jniCheckHasExceptionOccurredAndClear()) | |||
| { | |||
| // Failed to write to temporary file. | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| } | |||
| temporaryFilesFromAssetFiles.add (tempFile); | |||
| return URL (tempFile); | |||
| } | |||
| AsyncUpdater& owner; | |||
| Array<URL> fileUrls; | |||
| GlobalRef resultFileUris; | |||
| String packageName; | |||
| String uriBase; | |||
| StringArray filePaths; | |||
| Array<File> temporaryFilesFromAssetFiles; | |||
| StringArray mimeTypes; | |||
| }; | |||
| //============================================================================== | |||
| class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl, | |||
| public AndroidContentSharerFileObserver::Owner, | |||
| public AndroidContentSharerCursor::Owner, | |||
| public AsyncUpdater, | |||
| private Timer | |||
| { | |||
| public: | |||
| ContentSharerNativeImpl (ContentSharer& cs) | |||
| : owner (cs), | |||
| packageName (juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageName)))), | |||
| uriBase ("content://" + packageName + ".sharingcontentprovider/") | |||
| { | |||
| } | |||
| ~ContentSharerNativeImpl() override | |||
| { | |||
| masterReference.clear(); | |||
| } | |||
| void shareFiles (const Array<URL>& files) override | |||
| { | |||
| if (! isContentSharingEnabled()) | |||
| { | |||
| // You need to enable "Content Sharing" in Projucer's Android exporter. | |||
| jassertfalse; | |||
| owner.sharingFinished (false, {}); | |||
| } | |||
| prepareFilesThread.reset (new AndroidContentSharerPrepareFilesThread (*this, files, packageName, uriBase)); | |||
| } | |||
| void shareText (const String& text) override | |||
| { | |||
| if (! isContentSharingEnabled()) | |||
| { | |||
| // You need to enable "Content Sharing" in Projucer's Android exporter. | |||
| jassertfalse; | |||
| owner.sharingFinished (false, {}); | |||
| } | |||
| auto* env = getEnv(); | |||
| auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor)); | |||
| env->CallObjectMethod (intent, AndroidIntent.setAction, | |||
| javaString ("android.intent.action.SEND").get()); | |||
| env->CallObjectMethod (intent, AndroidIntent.putExtra, | |||
| javaString ("android.intent.extra.TEXT").get(), | |||
| javaString (text).get()); | |||
| env->CallObjectMethod (intent, AndroidIntent.setType, javaString ("text/plain").get()); | |||
| auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent, AndroidIntent.createChooser, | |||
| intent.get(), javaString ("Choose share target").get())); | |||
| startAndroidActivityForResult (chooserIntent, 1003, | |||
| [weakRef = WeakReference<ContentSharerNativeImpl> { this }] (int /*requestCode*/, | |||
| int resultCode, | |||
| LocalRef<jobject> /*intentData*/) mutable | |||
| { | |||
| if (weakRef != nullptr) | |||
| weakRef->sharingFinished (resultCode); | |||
| }); | |||
| } | |||
| //============================================================================== | |||
| void cursorClosed (const AndroidContentSharerCursor& cursor) override | |||
| { | |||
| cursors.removeObject (&cursor); | |||
| } | |||
| void fileHandleClosed (const AndroidContentSharerFileObserver&) override | |||
| { | |||
| decrementPendingFileCountAndNotifyOwnerIfReady(); | |||
| } | |||
| //============================================================================== | |||
| jobject openFile (const LocalRef<jobject>& contentProvider, | |||
| const LocalRef<jobject>& uri, const LocalRef<jstring>& mode) | |||
| { | |||
| ignoreUnused (mode); | |||
| WeakReference<ContentSharerNativeImpl> weakRef (this); | |||
| if (weakRef == nullptr) | |||
| return nullptr; | |||
| auto* env = getEnv(); | |||
| auto uriElements = getContentUriElements (env, uri); | |||
| if (uriElements.filepath.isEmpty()) | |||
| return nullptr; | |||
| return getAssetFileDescriptor (env, contentProvider, uriElements.filepath); | |||
| } | |||
| jobject query (const LocalRef<jobject>& contentProvider, const LocalRef<jobject>& uri, | |||
| const LocalRef<jobjectArray>& projection) | |||
| { | |||
| StringArray requestedColumns = javaStringArrayToJuce (projection); | |||
| StringArray supportedColumns = getSupportedColumns(); | |||
| StringArray resultColumns; | |||
| for (const auto& col : supportedColumns) | |||
| { | |||
| if (requestedColumns.contains (col)) | |||
| resultColumns.add (col); | |||
| } | |||
| // Unsupported columns were queried, file sharing may fail. | |||
| if (resultColumns.isEmpty()) | |||
| return nullptr; | |||
| auto resultJavaColumns = juceStringArrayToJava (resultColumns); | |||
| auto* env = getEnv(); | |||
| auto cursor = cursors.add (new AndroidContentSharerCursor (*this, env, contentProvider, | |||
| resultJavaColumns)); | |||
| auto uriElements = getContentUriElements (env, uri); | |||
| if (uriElements.filepath.isEmpty()) | |||
| return cursor->getNativeCursor(); | |||
| auto values = LocalRef<jobjectArray> (env->NewObjectArray ((jsize) resultColumns.size(), | |||
| JavaObject, nullptr)); | |||
| for (int i = 0; i < resultColumns.size(); ++i) | |||
| { | |||
| if (resultColumns.getReference (i) == "_display_name") | |||
| { | |||
| env->SetObjectArrayElement (values, i, javaString (uriElements.filename).get()); | |||
| } | |||
| else if (resultColumns.getReference (i) == "_size") | |||
| { | |||
| auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor, | |||
| javaString (uriElements.filepath).get())); | |||
| jlong fileLength = env->CallLongMethod (javaFile, JavaFile.length); | |||
| env->SetObjectArrayElement (values, i, env->NewObject (JavaLong, | |||
| JavaLong.constructor, | |||
| fileLength)); | |||
| } | |||
| } | |||
| cursor->addRow (values); | |||
| return cursor->getNativeCursor(); | |||
| } | |||
| jobjectArray getStreamTypes (const LocalRef<jobject>& uri, const LocalRef<jstring>& mimeTypeFilter) | |||
| { | |||
| auto* env = getEnv(); | |||
| auto extension = getContentUriElements (env, uri).filename.fromLastOccurrenceOf (".", false, true); | |||
| if (extension.isEmpty()) | |||
| return nullptr; | |||
| return juceStringArrayToJava (filterMimeTypes (getMimeTypesForFileExtension (extension), | |||
| juceString (mimeTypeFilter.get()))); | |||
| } | |||
| void sharingFinished (int resultCode) | |||
| { | |||
| sharingActivityDidFinish = true; | |||
| succeeded = resultCode == -1; | |||
| // Give content sharer a chance to request file access. | |||
| if (nonAssetFilesPendingShare.get() == 0) | |||
| startTimer (2000); | |||
| else | |||
| notifyOwnerIfReady(); | |||
| } | |||
| private: | |||
| bool isContentSharingEnabled() const | |||
| { | |||
| auto* env = getEnv(); | |||
| LocalRef<jobject> packageManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager)); | |||
| constexpr int getProviders = 8; | |||
| auto packageInfo = LocalRef<jobject> (env->CallObjectMethod (packageManager, | |||
| AndroidPackageManager.getPackageInfo, | |||
| javaString (packageName).get(), | |||
| getProviders)); | |||
| auto providers = LocalRef<jobjectArray> ((jobjectArray) env->GetObjectField (packageInfo, | |||
| AndroidPackageInfo.providers)); | |||
| if (providers == nullptr) | |||
| return false; | |||
| auto sharingContentProviderAuthority = packageName + ".sharingcontentprovider"; | |||
| const int numProviders = env->GetArrayLength (providers.get()); | |||
| for (int i = 0; i < numProviders; ++i) | |||
| { | |||
| auto providerInfo = LocalRef<jobject> (env->GetObjectArrayElement (providers, i)); | |||
| auto authority = LocalRef<jstring> ((jstring) env->GetObjectField (providerInfo, | |||
| AndroidProviderInfo.authority)); | |||
| if (juceString (authority) == sharingContentProviderAuthority) | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| void handleAsyncUpdate() override | |||
| { | |||
| jassert (prepareFilesThread != nullptr); | |||
| if (prepareFilesThread == nullptr) | |||
| return; | |||
| filesPrepared (prepareFilesThread->getResultFileUris(), prepareFilesThread->getMimeTypes()); | |||
| } | |||
| void filesPrepared (jobject fileUris, const StringArray& mimeTypes) | |||
| { | |||
| auto* env = getEnv(); | |||
| auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor)); | |||
| env->CallObjectMethod (intent, AndroidIntent.setAction, | |||
| javaString ("android.intent.action.SEND_MULTIPLE").get()); | |||
| env->CallObjectMethod (intent, AndroidIntent.setType, | |||
| javaString (getCommonMimeType (mimeTypes)).get()); | |||
| constexpr int grantReadPermission = 1; | |||
| env->CallObjectMethod (intent, AndroidIntent.setFlags, grantReadPermission); | |||
| env->CallObjectMethod (intent, AndroidIntent.putParcelableArrayListExtra, | |||
| javaString ("android.intent.extra.STREAM").get(), | |||
| fileUris); | |||
| auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent, | |||
| AndroidIntent.createChooser, | |||
| intent.get(), | |||
| javaString ("Choose share target").get())); | |||
| startAndroidActivityForResult (chooserIntent, 1003, | |||
| [weakRef = WeakReference<ContentSharerNativeImpl> { this }] (int /*requestCode*/, | |||
| int resultCode, | |||
| LocalRef<jobject> /*intentData*/) mutable | |||
| { | |||
| if (weakRef != nullptr) | |||
| weakRef->sharingFinished (resultCode); | |||
| }); | |||
| } | |||
| void decrementPendingFileCountAndNotifyOwnerIfReady() | |||
| { | |||
| --nonAssetFilesPendingShare; | |||
| notifyOwnerIfReady(); | |||
| } | |||
| void notifyOwnerIfReady() | |||
| { | |||
| if (sharingActivityDidFinish && nonAssetFilesPendingShare.get() == 0) | |||
| owner.sharingFinished (succeeded, {}); | |||
| } | |||
| void timerCallback() override | |||
| { | |||
| stopTimer(); | |||
| notifyOwnerIfReady(); | |||
| } | |||
| //============================================================================== | |||
| struct ContentUriElements | |||
| { | |||
| String index; | |||
| String filename; | |||
| String filepath; | |||
| }; | |||
| ContentUriElements getContentUriElements (JNIEnv* env, const LocalRef<jobject>& uri) const | |||
| { | |||
| jassert (prepareFilesThread != nullptr); | |||
| if (prepareFilesThread == nullptr) | |||
| return {}; | |||
| auto fullUri = juceString ((jstring) env->CallObjectMethod (uri.get(), AndroidUri.toString)); | |||
| auto index = fullUri.fromFirstOccurrenceOf (uriBase, false, false) | |||
| .upToFirstOccurrenceOf ("/", false, true); | |||
| auto filename = fullUri.fromLastOccurrenceOf ("/", false, true); | |||
| return { index, filename, prepareFilesThread->getFilePaths()[index.getIntValue()] }; | |||
| } | |||
| static StringArray getSupportedColumns() | |||
| { | |||
| return StringArray ("_display_name", "_size"); | |||
| } | |||
| jobject getAssetFileDescriptor (JNIEnv* env, const LocalRef<jobject>& contentProvider, | |||
| const String& filepath) | |||
| { | |||
| // This function can be called from multiple threads. | |||
| { | |||
| const ScopedLock sl (nonAssetFileOpenLock); | |||
| if (! nonAssetFilePathsPendingShare.contains (filepath)) | |||
| { | |||
| nonAssetFilePathsPendingShare.add (filepath); | |||
| ++nonAssetFilesPendingShare; | |||
| nonAssetFileObservers.add (new AndroidContentSharerFileObserver (*this, env, | |||
| contentProvider, | |||
| filepath)); | |||
| } | |||
| } | |||
| auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor, | |||
| javaString (filepath).get())); | |||
| constexpr int modeReadOnly = 268435456; | |||
| auto parcelFileDescriptor = LocalRef<jobject> (env->CallStaticObjectMethod (ParcelFileDescriptor, | |||
| ParcelFileDescriptor.open, | |||
| javaFile.get(), modeReadOnly)); | |||
| if (jniCheckHasExceptionOccurredAndClear()) | |||
| { | |||
| // Failed to create file descriptor. Have you provided a valid file path/resource name? | |||
| jassertfalse; | |||
| return nullptr; | |||
| } | |||
| jlong startOffset = 0; | |||
| jlong unknownLength = -1; | |||
| assetFileDescriptors.add (GlobalRef (LocalRef<jobject> (env->NewObject (AssetFileDescriptor, | |||
| AssetFileDescriptor.constructor, | |||
| parcelFileDescriptor.get(), | |||
| startOffset, unknownLength)))); | |||
| return assetFileDescriptors.getReference (assetFileDescriptors.size() - 1).get(); | |||
| } | |||
| StringArray filterMimeTypes (const StringArray& mimeTypes, const String& filter) | |||
| { | |||
| String filterToUse (filter.removeCharacters ("*")); | |||
| if (filterToUse.isEmpty() || filterToUse == "/") | |||
| return mimeTypes; | |||
| StringArray result; | |||
| for (const auto& type : mimeTypes) | |||
| if (String (type).contains (filterToUse)) | |||
| result.add (type); | |||
| return result; | |||
| } | |||
| String getCommonMimeType (const StringArray& mimeTypes) | |||
| { | |||
| if (mimeTypes.isEmpty()) | |||
| return "*/*"; | |||
| auto commonMime = mimeTypes[0]; | |||
| bool lookForCommonGroup = false; | |||
| for (int i = 1; i < mimeTypes.size(); ++i) | |||
| { | |||
| if (mimeTypes[i] == commonMime) | |||
| continue; | |||
| if (! lookForCommonGroup) | |||
| { | |||
| lookForCommonGroup = true; | |||
| commonMime = commonMime.upToFirstOccurrenceOf ("/", true, false); | |||
| } | |||
| if (! mimeTypes[i].startsWith (commonMime)) | |||
| return "*/*"; | |||
| } | |||
| return lookForCommonGroup ? commonMime + "*" : commonMime; | |||
| } | |||
| ContentSharer& owner; | |||
| String packageName; | |||
| String uriBase; | |||
| std::unique_ptr<AndroidContentSharerPrepareFilesThread> prepareFilesThread; | |||
| bool succeeded = false; | |||
| String errorDescription; | |||
| bool sharingActivityDidFinish = false; | |||
| OwnedArray<AndroidContentSharerCursor> cursors; | |||
| Array<GlobalRef> assetFileDescriptors; | |||
| CriticalSection nonAssetFileOpenLock; | |||
| StringArray nonAssetFilePathsPendingShare; | |||
| Atomic<int> nonAssetFilesPendingShare { 0 }; | |||
| OwnedArray<AndroidContentSharerFileObserver> nonAssetFileObservers; | |||
| WeakReference<ContentSharerNativeImpl>::Master masterReference; | |||
| friend class WeakReference<ContentSharerNativeImpl>; | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||
| CALLBACK (contentSharerQuery, "contentSharerQuery", "(Landroid/net/Uri;[Ljava/lang/String;)Landroid/database/Cursor;") \ | |||
| CALLBACK (contentSharerOpenFile, "contentSharerOpenFile", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;") \ | |||
| CALLBACK (contentSharerGetStreamTypes, "contentSharerGetStreamTypes", "(Landroid/net/Uri;Ljava/lang/String;)[Ljava/lang/String;") \ | |||
| DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceSharingContentProvider, "com/rmsl/juce/JuceSharingContentProvider", 16) | |||
| #undef JNI_CLASS_MEMBERS | |||
| static jobject JNICALL contentSharerQuery (JNIEnv*, jobject contentProvider, jobject uri, jobjectArray projection) | |||
| { | |||
| if (auto *pimpl = (ContentSharer::ContentSharerNativeImpl *) ContentSharer::getInstance ()->pimpl.get ()) | |||
| return pimpl->query (LocalRef<jobject> (static_cast<jobject> (contentProvider)), | |||
| LocalRef<jobject> (static_cast<jobject> (uri)), | |||
| LocalRef<jobjectArray> (static_cast<jobjectArray> (projection))); | |||
| return nullptr; | |||
| } | |||
| static jobject JNICALL contentSharerOpenFile (JNIEnv*, jobject contentProvider, jobject uri, jstring mode) | |||
| { | |||
| if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get()) | |||
| return pimpl->openFile (LocalRef<jobject> (static_cast<jobject> (contentProvider)), | |||
| LocalRef<jobject> (static_cast<jobject> (uri)), | |||
| LocalRef<jstring> (static_cast<jstring> (mode))); | |||
| return nullptr; | |||
| } | |||
| static jobjectArray JNICALL contentSharerGetStreamTypes (JNIEnv*, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter) | |||
| { | |||
| if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get()) | |||
| return pimpl->getStreamTypes (LocalRef<jobject> (static_cast<jobject> (uri)), | |||
| LocalRef<jstring> (static_cast<jstring> (mimeTypeFilter))); | |||
| return nullptr; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| ContentSharer::Pimpl* ContentSharer::createPimpl() | |||
| { | |||
| return new ContentSharerNativeImpl (*this); | |||
| } | |||
| ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider_Class ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider; | |||
| } // namespace juce | |||
| @@ -1,240 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 7 End-User License | |||
| Agreement and JUCE Privacy Policy. | |||
| End User License Agreement: www.juce.com/juce-7-licence | |||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class FileChooser::Native : public FileChooser::Pimpl | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| Native (FileChooser& fileChooser, int flags) : owner (fileChooser) | |||
| { | |||
| if (currentFileChooser == nullptr) | |||
| { | |||
| currentFileChooser = this; | |||
| auto* env = getEnv(); | |||
| auto sdkVersion = getAndroidSDKVersion(); | |||
| auto saveMode = ((flags & FileBrowserComponent::saveMode) != 0); | |||
| auto selectsDirectories = ((flags & FileBrowserComponent::canSelectDirectories) != 0); | |||
| // You cannot save a directory | |||
| jassert (! (saveMode && selectsDirectories)); | |||
| if (sdkVersion < 19) | |||
| { | |||
| // native save dialogs are only supported in Android versions >= 19 | |||
| jassert (! saveMode); | |||
| saveMode = false; | |||
| } | |||
| if (sdkVersion < 21) | |||
| { | |||
| // native directory chooser dialogs are only supported in Android versions >= 21 | |||
| jassert (! selectsDirectories); | |||
| selectsDirectories = false; | |||
| } | |||
| const char* action = (selectsDirectories ? "android.intent.action.OPEN_DOCUMENT_TREE" | |||
| : (saveMode ? "android.intent.action.CREATE_DOCUMENT" | |||
| : (sdkVersion >= 19 ? "android.intent.action.OPEN_DOCUMENT" | |||
| : "android.intent.action.GET_CONTENT"))); | |||
| intent = GlobalRef (LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructWithString, | |||
| javaString (action).get()))); | |||
| if (owner.startingFile != File()) | |||
| { | |||
| if (saveMode && (! owner.startingFile.isDirectory())) | |||
| env->CallObjectMethod (intent.get(), AndroidIntent.putExtraString, | |||
| javaString ("android.intent.extra.TITLE").get(), | |||
| javaString (owner.startingFile.getFileName()).get()); | |||
| URL url (owner.startingFile); | |||
| LocalRef<jobject> uri (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, | |||
| javaString (url.toString (true)).get())); | |||
| if (uri) | |||
| env->CallObjectMethod (intent.get(), AndroidIntent.putExtraParcelable, | |||
| javaString ("android.provider.extra.INITIAL_URI").get(), | |||
| uri.get()); | |||
| } | |||
| if (! selectsDirectories) | |||
| { | |||
| env->CallObjectMethod (intent.get(), AndroidIntent.addCategory, | |||
| javaString ("android.intent.category.OPENABLE").get()); | |||
| auto mimeTypes = convertFiltersToMimeTypes (owner.filters); | |||
| if (mimeTypes.size() == 1) | |||
| { | |||
| env->CallObjectMethod (intent.get(), AndroidIntent.setType, javaString (mimeTypes[0]).get()); | |||
| } | |||
| else | |||
| { | |||
| String mimeGroup = "*"; | |||
| if (mimeTypes.size() > 0) | |||
| { | |||
| mimeGroup = mimeTypes[0].upToFirstOccurrenceOf ("/", false, false); | |||
| auto allMimeTypesHaveSameGroup = true; | |||
| LocalRef<jobjectArray> jMimeTypes (env->NewObjectArray (mimeTypes.size(), JavaString, | |||
| javaString("").get())); | |||
| for (int i = 0; i < mimeTypes.size(); ++i) | |||
| { | |||
| env->SetObjectArrayElement (jMimeTypes.get(), i, javaString (mimeTypes[i]).get()); | |||
| if (mimeGroup != mimeTypes[i].upToFirstOccurrenceOf ("/", false, false)) | |||
| allMimeTypesHaveSameGroup = false; | |||
| } | |||
| env->CallObjectMethod (intent.get(), AndroidIntent.putExtraStrings, | |||
| javaString ("android.intent.extra.MIME_TYPES").get(), | |||
| jMimeTypes.get()); | |||
| if (! allMimeTypesHaveSameGroup) | |||
| mimeGroup = "*"; | |||
| } | |||
| env->CallObjectMethod (intent.get(), AndroidIntent.setType, javaString (mimeGroup + "/*").get()); | |||
| } | |||
| } | |||
| } | |||
| else | |||
| jassertfalse; // there can only be a single file chooser | |||
| } | |||
| ~Native() override | |||
| { | |||
| masterReference.clear(); | |||
| currentFileChooser = nullptr; | |||
| } | |||
| void runModally() override | |||
| { | |||
| // Android does not support modal file choosers | |||
| jassertfalse; | |||
| } | |||
| void launch() override | |||
| { | |||
| auto* env = getEnv(); | |||
| if (currentFileChooser != nullptr) | |||
| { | |||
| startAndroidActivityForResult (LocalRef<jobject> (env->NewLocalRef (intent.get())), /*READ_REQUEST_CODE*/ 42, | |||
| [myself = WeakReference<Native> { this }] (int requestCode, int resultCode, LocalRef<jobject> intentData) mutable | |||
| { | |||
| if (myself != nullptr) | |||
| myself->onActivityResult (requestCode, resultCode, intentData); | |||
| }); | |||
| } | |||
| else | |||
| { | |||
| jassertfalse; // There is already a file chooser running | |||
| } | |||
| } | |||
| void onActivityResult (int /*requestCode*/, int resultCode, const LocalRef<jobject>& intentData) | |||
| { | |||
| currentFileChooser = nullptr; | |||
| auto* env = getEnv(); | |||
| Array<URL> chosenURLs; | |||
| if (resultCode == /*Activity.RESULT_OK*/ -1 && intentData != nullptr) | |||
| { | |||
| LocalRef<jobject> uri (env->CallObjectMethod (intentData.get(), AndroidIntent.getData)); | |||
| if (uri != nullptr) | |||
| { | |||
| auto jStr = (jstring) env->CallObjectMethod (uri, JavaObject.toString); | |||
| if (jStr != nullptr) | |||
| chosenURLs.add (URL (juceString (env, jStr))); | |||
| } | |||
| } | |||
| owner.finished (chosenURLs); | |||
| } | |||
| static Native* currentFileChooser; | |||
| static StringArray convertFiltersToMimeTypes (const String& fileFilters) | |||
| { | |||
| StringArray result; | |||
| auto wildcards = StringArray::fromTokens (fileFilters, ";", ""); | |||
| for (auto wildcard : wildcards) | |||
| { | |||
| if (wildcard.upToLastOccurrenceOf (".", false, false) == "*") | |||
| { | |||
| auto extension = wildcard.fromLastOccurrenceOf (".", false, false); | |||
| result.addArray (getMimeTypesForFileExtension (extension)); | |||
| } | |||
| } | |||
| result.removeDuplicates (false); | |||
| return result; | |||
| } | |||
| private: | |||
| JUCE_DECLARE_WEAK_REFERENCEABLE (Native) | |||
| FileChooser& owner; | |||
| GlobalRef intent; | |||
| }; | |||
| FileChooser::Native* FileChooser::Native::currentFileChooser = nullptr; | |||
| std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags, | |||
| FilePreviewComponent*) | |||
| { | |||
| if (FileChooser::Native::currentFileChooser == nullptr) | |||
| return std::make_shared<FileChooser::Native> (owner, flags); | |||
| // there can only be one file chooser on Android at a once | |||
| jassertfalse; | |||
| return nullptr; | |||
| } | |||
| bool FileChooser::isPlatformDialogAvailable() | |||
| { | |||
| #if JUCE_DISABLE_NATIVE_FILECHOOSERS | |||
| return false; | |||
| #else | |||
| return true; | |||
| #endif | |||
| } | |||
| } // namespace juce | |||
| @@ -1,211 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 7 End-User License | |||
| Agreement and JUCE Privacy Policy. | |||
| End User License Agreement: www.juce.com/juce-7-licence | |||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| #if ! defined (__IPHONE_10_0) || __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_10_0 | |||
| using UIActivityType = NSString*; | |||
| #endif | |||
| class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl, | |||
| private Component | |||
| { | |||
| public: | |||
| ContentSharerNativeImpl (ContentSharer& cs) | |||
| : owner (cs) | |||
| { | |||
| static PopoverDelegateClass cls; | |||
| popoverDelegate.reset ([cls.createInstance() init]); | |||
| } | |||
| ~ContentSharerNativeImpl() override | |||
| { | |||
| exitModalState (0); | |||
| } | |||
| void shareFiles (const Array<URL>& files) override | |||
| { | |||
| auto urls = [NSMutableArray arrayWithCapacity: (NSUInteger) files.size()]; | |||
| for (const auto& f : files) | |||
| { | |||
| NSString* nativeFilePath = nil; | |||
| if (f.isLocalFile()) | |||
| { | |||
| nativeFilePath = juceStringToNS (f.getLocalFile().getFullPathName()); | |||
| } | |||
| else | |||
| { | |||
| auto filePath = f.toString (false); | |||
| auto* fileDirectory = filePath.contains ("/") | |||
| ? juceStringToNS (filePath.upToLastOccurrenceOf ("/", false, false)) | |||
| : [NSString string]; | |||
| auto fileName = juceStringToNS (filePath.fromLastOccurrenceOf ("/", false, false) | |||
| .upToLastOccurrenceOf (".", false, false)); | |||
| auto fileExt = juceStringToNS (filePath.fromLastOccurrenceOf (".", false, false)); | |||
| if ([fileDirectory length] == NSUInteger (0)) | |||
| nativeFilePath = [[NSBundle mainBundle] pathForResource: fileName | |||
| ofType: fileExt]; | |||
| else | |||
| nativeFilePath = [[NSBundle mainBundle] pathForResource: fileName | |||
| ofType: fileExt | |||
| inDirectory: fileDirectory]; | |||
| } | |||
| if (nativeFilePath != nil) | |||
| [urls addObject: [NSURL fileURLWithPath: nativeFilePath]]; | |||
| } | |||
| share (urls); | |||
| } | |||
| void shareText (const String& text) override | |||
| { | |||
| auto array = [NSArray arrayWithObject: juceStringToNS (text)]; | |||
| share (array); | |||
| } | |||
| private: | |||
| void share (NSArray* items) | |||
| { | |||
| if ([items count] == 0) | |||
| { | |||
| jassertfalse; | |||
| owner.sharingFinished (false, "No valid items found for sharing."); | |||
| return; | |||
| } | |||
| controller.reset ([[UIActivityViewController alloc] initWithActivityItems: items | |||
| applicationActivities: nil]); | |||
| controller.get().excludedActivityTypes = nil; | |||
| controller.get().completionWithItemsHandler = ^ (UIActivityType type, BOOL completed, | |||
| NSArray* returnedItems, NSError* error) | |||
| { | |||
| ignoreUnused (type); | |||
| ignoreUnused (returnedItems); | |||
| succeeded = completed; | |||
| if (error != nil) | |||
| errorDescription = nsStringToJuce ([error localizedDescription]); | |||
| exitModalState (0); | |||
| }; | |||
| controller.get().modalTransitionStyle = UIModalTransitionStyleCoverVertical; | |||
| auto bounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; | |||
| setBounds (bounds); | |||
| setAlwaysOnTop (true); | |||
| setVisible (true); | |||
| addToDesktop (0); | |||
| enterModalState (true, | |||
| ModalCallbackFunction::create ([this] (int) | |||
| { | |||
| owner.sharingFinished (succeeded, errorDescription); | |||
| }), | |||
| false); | |||
| } | |||
| static bool isIPad() | |||
| { | |||
| return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad; | |||
| } | |||
| //============================================================================== | |||
| void parentHierarchyChanged() override | |||
| { | |||
| auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer()); | |||
| if (peer != newPeer) | |||
| { | |||
| peer = newPeer; | |||
| if (isIPad()) | |||
| { | |||
| controller.get().preferredContentSize = peer->view.frame.size; | |||
| auto screenBounds = [UIScreen mainScreen].bounds; | |||
| auto* popoverController = controller.get().popoverPresentationController; | |||
| popoverController.sourceView = peer->view; | |||
| popoverController.sourceRect = CGRectMake (0.f, screenBounds.size.height - 10.f, screenBounds.size.width, 10.f); | |||
| popoverController.canOverlapSourceViewRect = YES; | |||
| popoverController.delegate = popoverDelegate.get(); | |||
| } | |||
| if (auto* parentController = peer->controller) | |||
| [parentController showViewController: controller.get() sender: parentController]; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| struct PopoverDelegateClass : public ObjCClass<NSObject<UIPopoverPresentationControllerDelegate>> | |||
| { | |||
| PopoverDelegateClass() : ObjCClass<NSObject<UIPopoverPresentationControllerDelegate>> ("PopoverDelegateClass_") | |||
| { | |||
| addMethod (@selector (popoverPresentationController:willRepositionPopoverToRect:inView:), willRepositionPopover); | |||
| registerClass(); | |||
| } | |||
| //============================================================================== | |||
| static void willRepositionPopover (id, SEL, UIPopoverPresentationController*, CGRect* rect, UIView*) | |||
| { | |||
| auto screenBounds = [UIScreen mainScreen].bounds; | |||
| rect->origin.x = 0.f; | |||
| rect->origin.y = screenBounds.size.height - 10.f; | |||
| rect->size.width = screenBounds.size.width; | |||
| rect->size.height = 10.f; | |||
| } | |||
| }; | |||
| ContentSharer& owner; | |||
| UIViewComponentPeer* peer = nullptr; | |||
| NSUniquePtr<UIActivityViewController> controller; | |||
| NSUniquePtr<NSObject<UIPopoverPresentationControllerDelegate>> popoverDelegate; | |||
| bool succeeded = false; | |||
| String errorDescription; | |||
| }; | |||
| //============================================================================== | |||
| ContentSharer::Pimpl* ContentSharer::createPimpl() | |||
| { | |||
| return new ContentSharerNativeImpl (*this); | |||
| } | |||
| } // namespace juce | |||
| @@ -1,412 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 7 End-User License | |||
| Agreement and JUCE Privacy Policy. | |||
| End User License Agreement: www.juce.com/juce-7-licence | |||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| #if ! (defined (__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0) | |||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
| #define JUCE_DEPRECATION_IGNORED 1 | |||
| #endif | |||
| class FileChooser::Native : public FileChooser::Pimpl, | |||
| public Component, | |||
| private AsyncUpdater | |||
| { | |||
| public: | |||
| Native (FileChooser& fileChooser, int flags) | |||
| : owner (fileChooser) | |||
| { | |||
| static FileChooserDelegateClass delegateClass; | |||
| delegate.reset ([delegateClass.createInstance() init]); | |||
| FileChooserDelegateClass::setOwner (delegate.get(), this); | |||
| static FileChooserControllerClass controllerClass; | |||
| auto* controllerClassInstance = controllerClass.createInstance(); | |||
| String firstFileExtension; | |||
| auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension)); | |||
| if ((flags & FileBrowserComponent::saveMode) != 0) | |||
| { | |||
| auto currentFileOrDirectory = owner.startingFile; | |||
| UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile() | |||
| ? UIDocumentPickerModeExportToService | |||
| : UIDocumentPickerModeMoveToService; | |||
| if (! currentFileOrDirectory.existsAsFile()) | |||
| { | |||
| auto filename = getFilename (currentFileOrDirectory, firstFileExtension); | |||
| auto tmpDirectory = File::createTempFile ("JUCE-filepath"); | |||
| if (tmpDirectory.createDirectory().wasOk()) | |||
| { | |||
| currentFileOrDirectory = tmpDirectory.getChildFile (filename); | |||
| currentFileOrDirectory.replaceWithText (""); | |||
| } | |||
| else | |||
| { | |||
| // Temporary directory creation failed! You need to specify a | |||
| // path you have write access to. Saving will not work for | |||
| // current path. | |||
| jassertfalse; | |||
| } | |||
| } | |||
| auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())]; | |||
| controller.reset ([controllerClassInstance initWithURL: url | |||
| inMode: pickerMode]); | |||
| [url release]; | |||
| } | |||
| else | |||
| { | |||
| controller.reset ([controllerClassInstance initWithDocumentTypes: utTypeArray | |||
| inMode: UIDocumentPickerModeOpen]); | |||
| if (@available (iOS 11.0, *)) | |||
| [controller.get() setAllowsMultipleSelection: (flags & FileBrowserComponent::canSelectMultipleItems) != 0]; | |||
| } | |||
| FileChooserControllerClass::setOwner (controller.get(), this); | |||
| [controller.get() setDelegate: delegate.get()]; | |||
| [controller.get() setModalTransitionStyle: UIModalTransitionStyleCrossDissolve]; | |||
| setOpaque (false); | |||
| if (fileChooser.parent != nullptr) | |||
| { | |||
| [controller.get() setModalPresentationStyle: UIModalPresentationFullScreen]; | |||
| auto chooserBounds = fileChooser.parent->getBounds(); | |||
| setBounds (chooserBounds); | |||
| setAlwaysOnTop (true); | |||
| fileChooser.parent->addAndMakeVisible (this); | |||
| } | |||
| else | |||
| { | |||
| if (SystemStats::isRunningInAppExtensionSandbox()) | |||
| { | |||
| // Opening a native top-level window in an AUv3 is not allowed (sandboxing). You need to specify a | |||
| // parent component (for example your editor) to parent the native file chooser window. To do this | |||
| // specify a parent component in the FileChooser's constructor! | |||
| jassertfalse; | |||
| return; | |||
| } | |||
| auto chooserBounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; | |||
| setBounds (chooserBounds); | |||
| setAlwaysOnTop (true); | |||
| setVisible (true); | |||
| addToDesktop (0); | |||
| } | |||
| } | |||
| ~Native() override | |||
| { | |||
| exitModalState (0); | |||
| // Our old peer may not have received a becomeFirstResponder call at this point, | |||
| // so the static currentlyFocusedPeer may be null. | |||
| // We'll try to find an appropriate peer to focus. | |||
| for (auto i = 0; i < ComponentPeer::getNumPeers(); ++i) | |||
| if (auto* p = ComponentPeer::getPeer (i)) | |||
| if (p != getPeer()) | |||
| if (auto* view = (UIView*) p->getNativeHandle()) | |||
| [view becomeFirstResponder]; | |||
| } | |||
| void launch() override | |||
| { | |||
| enterModalState (true, nullptr, true); | |||
| } | |||
| void runModally() override | |||
| { | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| runModalLoop(); | |||
| #else | |||
| jassertfalse; | |||
| #endif | |||
| } | |||
| void parentHierarchyChanged() override | |||
| { | |||
| auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer()); | |||
| if (peer != newPeer) | |||
| { | |||
| peer = newPeer; | |||
| if (peer != nullptr) | |||
| { | |||
| if (auto* parentController = peer->controller) | |||
| [parentController showViewController: controller.get() sender: parentController]; | |||
| peer->toFront (false); | |||
| } | |||
| } | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| void handleAsyncUpdate() override | |||
| { | |||
| pickerWasCancelled(); | |||
| } | |||
| //============================================================================== | |||
| static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension) | |||
| { | |||
| auto filters = StringArray::fromTokens (filterWildcards, ";", ""); | |||
| StringArray result; | |||
| firstExtension = {}; | |||
| if (! filters.contains ("*") && filters.size() > 0) | |||
| { | |||
| for (auto filter : filters) | |||
| { | |||
| if (filter.isEmpty()) | |||
| continue; | |||
| // iOS only supports file extension wild cards | |||
| jassert (filter.upToLastOccurrenceOf (".", true, false) == "*."); | |||
| auto fileExtension = filter.fromLastOccurrenceOf (".", false, false); | |||
| CFUniquePtr<CFStringRef> fileExtensionCF (fileExtension.toCFString()); | |||
| if (firstExtension.isEmpty()) | |||
| firstExtension = fileExtension; | |||
| if (auto tag = CFUniquePtr<CFStringRef> (UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF.get(), nullptr))) | |||
| result.add (String::fromCFString (tag.get())); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| result.add ("public.data"); | |||
| } | |||
| return result; | |||
| } | |||
| static String getFilename (const File& path, const String& fallbackExtension) | |||
| { | |||
| auto filename = path.getFileNameWithoutExtension(); | |||
| auto extension = path.getFileExtension().substring (1); | |||
| if (filename.isEmpty()) | |||
| filename = "Untitled"; | |||
| if (extension.isEmpty()) | |||
| extension = fallbackExtension; | |||
| if (extension.isNotEmpty()) | |||
| filename += "." + extension; | |||
| return filename; | |||
| } | |||
| //============================================================================== | |||
| void didPickDocumentsAtURLs (NSArray<NSURL*>* urls) | |||
| { | |||
| cancelPendingUpdate(); | |||
| const auto isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService | |||
| || controller.get().documentPickerMode == UIDocumentPickerModeMoveToService; | |||
| const auto accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges; | |||
| auto* fileCoordinator = [[[NSFileCoordinator alloc] initWithFilePresenter: nil] autorelease]; | |||
| auto* intents = [[[NSMutableArray alloc] init] autorelease]; | |||
| for (NSURL* url in urls) | |||
| { | |||
| auto* fileAccessIntent = isWriting | |||
| ? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions] | |||
| : [NSFileAccessIntent readingIntentWithURL: url options: accessOptions]; | |||
| [intents addObject: fileAccessIntent]; | |||
| } | |||
| [fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err) | |||
| { | |||
| if (err != nil) | |||
| { | |||
| auto desc = [err localizedDescription]; | |||
| ignoreUnused (desc); | |||
| jassertfalse; | |||
| return; | |||
| } | |||
| Array<URL> result; | |||
| for (NSURL* url in urls) | |||
| { | |||
| [url startAccessingSecurityScopedResource]; | |||
| NSError* error = nil; | |||
| auto* bookmark = [url bookmarkDataWithOptions: 0 | |||
| includingResourceValuesForKeys: nil | |||
| relativeToURL: nil | |||
| error: &error]; | |||
| [bookmark retain]; | |||
| [url stopAccessingSecurityScopedResource]; | |||
| URL juceUrl (nsStringToJuce ([url absoluteString])); | |||
| if (error == nil) | |||
| { | |||
| setURLBookmark (juceUrl, (void*) bookmark); | |||
| } | |||
| else | |||
| { | |||
| auto desc = [error localizedDescription]; | |||
| ignoreUnused (desc); | |||
| jassertfalse; | |||
| } | |||
| result.add (std::move (juceUrl)); | |||
| } | |||
| owner.finished (std::move (result)); | |||
| }]; | |||
| } | |||
| void didPickDocumentAtURL (NSURL* url) | |||
| { | |||
| didPickDocumentsAtURLs (@[url]); | |||
| } | |||
| void pickerWasCancelled() | |||
| { | |||
| cancelPendingUpdate(); | |||
| owner.finished ({}); | |||
| // Calling owner.finished will delete this Pimpl instance, so don't call any more member functions here! | |||
| } | |||
| //============================================================================== | |||
| struct FileChooserDelegateClass : public ObjCClass<NSObject<UIDocumentPickerDelegate>> | |||
| { | |||
| FileChooserDelegateClass() : ObjCClass<NSObject<UIDocumentPickerDelegate>> ("FileChooserDelegate_") | |||
| { | |||
| addIvar<Native*> ("owner"); | |||
| addMethod (@selector (documentPicker:didPickDocumentAtURL:), didPickDocumentAtURL); | |||
| addMethod (@selector (documentPicker:didPickDocumentsAtURLs:), didPickDocumentsAtURLs); | |||
| addMethod (@selector (documentPickerWasCancelled:), documentPickerWasCancelled); | |||
| addProtocol (@protocol (UIDocumentPickerDelegate)); | |||
| registerClass(); | |||
| } | |||
| static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); } | |||
| static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); } | |||
| //============================================================================== | |||
| static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url) | |||
| { | |||
| if (auto* picker = getOwner (self)) | |||
| picker->didPickDocumentAtURL (url); | |||
| } | |||
| static void didPickDocumentsAtURLs (id self, SEL, UIDocumentPickerViewController*, NSArray<NSURL*>* urls) | |||
| { | |||
| if (auto* picker = getOwner (self)) | |||
| picker->didPickDocumentsAtURLs (urls); | |||
| } | |||
| static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*) | |||
| { | |||
| if (auto* picker = getOwner (self)) | |||
| picker->pickerWasCancelled(); | |||
| } | |||
| }; | |||
| struct FileChooserControllerClass : public ObjCClass<UIDocumentPickerViewController> | |||
| { | |||
| FileChooserControllerClass() : ObjCClass<UIDocumentPickerViewController> ("FileChooserController_") | |||
| { | |||
| addIvar<Native*> ("owner"); | |||
| addMethod (@selector (viewDidDisappear:), viewDidDisappear); | |||
| registerClass(); | |||
| } | |||
| static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); } | |||
| static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); } | |||
| //============================================================================== | |||
| static void viewDidDisappear (id self, SEL, BOOL animated) | |||
| { | |||
| sendSuperclassMessage<void> (self, @selector (viewDidDisappear:), animated); | |||
| if (auto* picker = getOwner (self)) | |||
| picker->triggerAsyncUpdate(); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| FileChooser& owner; | |||
| NSUniquePtr<NSObject<UIDocumentPickerDelegate>> delegate; | |||
| NSUniquePtr<UIDocumentPickerViewController> controller; | |||
| UIViewComponentPeer* peer = nullptr; | |||
| static FileChooserDelegateClass fileChooserDelegateClass; | |||
| static FileChooserControllerClass fileChooserControllerClass; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) | |||
| }; | |||
| //============================================================================== | |||
| bool FileChooser::isPlatformDialogAvailable() | |||
| { | |||
| #if JUCE_DISABLE_NATIVE_FILECHOOSERS | |||
| return false; | |||
| #else | |||
| return true; | |||
| #endif | |||
| } | |||
| std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags, | |||
| FilePreviewComponent*) | |||
| { | |||
| return std::make_shared<FileChooser::Native> (owner, flags); | |||
| } | |||
| #if JUCE_DEPRECATION_IGNORED | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| #endif | |||
| } // namespace juce | |||