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 |