Browse Source

Update to JUCE 7.0.1, still to update to new playhead APIs

Signed-off-by: falkTX <falktx@falktx.com>
tags/v2.5.0
falkTX 2 years ago
parent
commit
08af009062
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
100 changed files with 2918 additions and 17224 deletions
  1. +0
    -28
      data/copy-juce
  2. +33
    -0
      data/update-juce
  3. +7
    -2
      source/backend/plugin/CarlaPluginJuce.cpp
  4. +2
    -0
      source/modules/AppConfig.h
  5. +320
    -3
      source/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h
  6. +38
    -55
      source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
  7. +30
    -32
      source/modules/juce_audio_basics/juce_audio_basics.cpp
  8. +2
    -2
      source/modules/juce_audio_basics/juce_audio_basics.h
  9. +2
    -2
      source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp
  10. +2
    -2
      source/modules/juce_audio_devices/juce_audio_devices.cpp
  11. +1
    -1
      source/modules/juce_audio_devices/juce_audio_devices.h
  12. +0
    -470
      source/modules/juce_audio_devices/native/juce_android_Audio.cpp
  13. +0
    -131
      source/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h
  14. +0
    -701
      source/modules/juce_audio_devices/native/juce_android_Midi.cpp
  15. +0
    -1439
      source/modules/juce_audio_devices/native/juce_android_Oboe.cpp
  16. +0
    -1292
      source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp
  17. +0
    -1495
      source/modules/juce_audio_devices/native/juce_ios_Audio.cpp
  18. +0
    -92
      source/modules/juce_audio_devices/native/juce_ios_Audio.h
  19. +7
    -3
      source/modules/juce_audio_processors/format_types/LV2_SDK/generate_lv2_bundle_sources.py
  20. +31
    -0
      source/modules/juce_audio_processors/format_types/juce_ARAHosting.h
  21. +4
    -4
      source/modules/juce_audio_processors/format_types/juce_AU_Shared.h
  22. +12
    -12
      source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm
  23. +4
    -11
      source/modules/juce_audio_processors/format_types/juce_LV2Common.h
  24. +183
    -151
      source/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp
  25. +4
    -0
      source/modules/juce_audio_processors/format_types/juce_LV2Resources.h
  26. +336
    -159
      source/modules/juce_audio_processors/format_types/juce_VST3Common.h
  27. +141
    -139
      source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp
  28. +274
    -159
      source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat_test.cpp
  29. +56
    -35
      source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp
  30. +34
    -35
      source/modules/juce_audio_processors/juce_audio_processors.cpp
  31. +1
    -1
      source/modules/juce_audio_processors/juce_audio_processors.h
  32. +0
    -53
      source/modules/juce_audio_processors/processors/juce_AudioProcessor.h
  33. +0
    -3
      source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp
  34. +0
    -3
      source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h
  35. +5
    -19
      source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp
  36. +12
    -12
      source/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h
  37. +5
    -5
      source/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.h
  38. +1
    -1
      source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp
  39. +4
    -4
      source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h
  40. +5
    -7
      source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp
  41. +1
    -1
      source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h
  42. +8
    -0
      source/modules/juce_audio_processors/utilities/juce_ExtensionsVisitor.h
  43. +41
    -18
      source/modules/juce_core/containers/juce_Optional.h
  44. +476
    -0
      source/modules/juce_core/files/juce_AndroidDocument.h
  45. +7
    -0
      source/modules/juce_core/files/juce_File.cpp
  46. +6
    -0
      source/modules/juce_core/files/juce_File.h
  47. +20
    -3
      source/modules/juce_core/files/juce_common_MimeTypes.cpp
  48. +19
    -7
      source/modules/juce_core/files/juce_common_MimeTypes.h
  49. +23
    -21
      source/modules/juce_core/juce_core.cpp
  50. +3
    -1
      source/modules/juce_core/juce_core.h
  51. +2
    -0
      source/modules/juce_core/native/juce_BasicNativeHeaders.h
  52. +0
    -691
      source/modules/juce_core/native/juce_android_Files.cpp
  53. +0
    -701
      source/modules/juce_core/native/juce_android_JNIHelpers.cpp
  54. +0
    -1010
      source/modules/juce_core/native/juce_android_JNIHelpers.h
  55. +0
    -44
      source/modules/juce_core/native/juce_android_Misc.cpp
  56. +0
    -656
      source/modules/juce_core/native/juce_android_Network.cpp
  57. +0
    -261
      source/modules/juce_core/native/juce_android_RuntimePermissions.cpp
  58. +0
    -241
      source/modules/juce_core/native/juce_android_SystemStats.cpp
  59. +0
    -395
      source/modules/juce_core/native/juce_android_Threads.cpp
  60. +1
    -1
      source/modules/juce_core/native/juce_mac_ObjCHelpers.h
  61. +6
    -0
      source/modules/juce_core/native/juce_posix_SharedCode.h
  62. +1
    -1
      source/modules/juce_core/native/juce_win32_ComSmartPtr.h
  63. +94
    -9
      source/modules/juce_core/native/juce_win32_Files.cpp
  64. +5
    -4
      source/modules/juce_core/network/juce_URL.cpp
  65. +2
    -2
      source/modules/juce_core/system/juce_CompilerSupport.h
  66. +8
    -3
      source/modules/juce_core/system/juce_StandardHeader.h
  67. +1
    -1
      source/modules/juce_core/threads/juce_ChildProcess.cpp
  68. +1
    -1
      source/modules/juce_core/threads/juce_ChildProcess.h
  69. +1
    -1
      source/modules/juce_data_structures/juce_data_structures.h
  70. +4
    -0
      source/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp
  71. +6
    -6
      source/modules/juce_events/juce_events.cpp
  72. +1
    -1
      source/modules/juce_events/juce_events.h
  73. +2
    -0
      source/modules/juce_events/messages/juce_MessageManager.cpp
  74. +0
    -302
      source/modules/juce_events/native/juce_android_Messaging.cpp
  75. +0
    -103
      source/modules/juce_events/native/juce_ios_MessageManager.mm
  76. +4
    -4
      source/modules/juce_graphics/juce_graphics.cpp
  77. +1
    -1
      source/modules/juce_graphics/juce_graphics.h
  78. +0
    -549
      source/modules/juce_graphics/native/juce_android_Fonts.cpp
  79. +0
    -66
      source/modules/juce_graphics/native/juce_android_GraphicsContext.cpp
  80. +3
    -0
      source/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTextInterface.h
  81. +0
    -1
      source/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp
  82. +21
    -1
      source/modules/juce_gui_basics/components/juce_Component.cpp
  83. +67
    -62
      source/modules/juce_gui_basics/juce_gui_basics.cpp
  84. +1
    -1
      source/modules/juce_gui_basics/juce_gui_basics.h
  85. +13
    -2
      source/modules/juce_gui_basics/layout/juce_Grid.cpp
  86. +22
    -5
      source/modules/juce_gui_basics/layout/juce_Viewport.cpp
  87. +11
    -1
      source/modules/juce_gui_basics/layout/juce_Viewport.h
  88. +3
    -2
      source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp
  89. +9
    -3
      source/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp
  90. +232
    -40
      source/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h
  91. +150
    -125
      source/modules/juce_gui_basics/native/accessibility/juce_android_Accessibility.cpp
  92. +7
    -3
      source/modules/juce_gui_basics/native/accessibility/juce_win32_Accessibility.cpp
  93. +14
    -0
      source/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h
  94. +65
    -39
      source/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h
  95. +0
    -900
      source/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp
  96. +0
    -240
      source/modules/juce_gui_basics/native/juce_android_FileChooser.cpp
  97. +0
    -2108
      source/modules/juce_gui_basics/native/juce_android_Windowing.cpp
  98. +0
    -211
      source/modules/juce_gui_basics/native/juce_ios_ContentSharer.cpp
  99. +0
    -412
      source/modules/juce_gui_basics/native/juce_ios_FileChooser.mm
  100. +0
    -1400
      source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm

+ 0
- 28
data/copy-juce View File

@@ -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

+ 33
- 0
data/update-juce View File

@@ -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}

+ 7
- 2
source/backend/plugin/CarlaPluginJuce.cpp View File

@@ -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 {};
}

// -------------------------------------------------------------------


+ 2
- 0
source/modules/AppConfig.h View File

@@ -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


+ 320
- 3
source/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h View File

@@ -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; }


+ 38
- 55
source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h View File

@@ -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)
};


+ 30
- 32
source/modules/juce_audio_basics/juce_audio_basics.cpp View File

@@ -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"


+ 2
- 2
source/modules/juce_audio_basics/juce_audio_basics.h View File

@@ -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"

+ 2
- 2
source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp View File

@@ -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();
}


+ 2
- 2
source/modules/juce_audio_devices/juce_audio_devices.cpp View File

@@ -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"

+ 1
- 1
source/modules/juce_audio_devices/juce_audio_devices.h View File

@@ -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


+ 0
- 470
source/modules/juce_audio_devices/native/juce_android_Audio.cpp View File

@@ -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

+ 0
- 131
source/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h View File

@@ -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

+ 0
- 701
source/modules/juce_audio_devices/native/juce_android_Midi.cpp View File

@@ -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

+ 0
- 1439
source/modules/juce_audio_devices/native/juce_android_Oboe.cpp
File diff suppressed because it is too large
View File


+ 0
- 1292
source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp
File diff suppressed because it is too large
View File


+ 0
- 1495
source/modules/juce_audio_devices/native/juce_ios_Audio.cpp
File diff suppressed because it is too large
View File


+ 0
- 92
source/modules/juce_audio_devices/native/juce_ios_Audio.h View File

@@ -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

+ 7
- 3
source/modules/juce_audio_processors/format_types/LV2_SDK/generate_lv2_bundle_sources.py View File

@@ -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")

+ 31
- 0
source/modules/juce_audio_processors/format_types/juce_ARAHosting.h View File

@@ -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:


+ 4
- 4
source/modules/juce_audio_processors/format_types/juce_AU_Shared.h View File

@@ -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

+ 12
- 12
source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm View File

@@ -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;
}
}


+ 4
- 11
source/modules/juce_audio_processors/format_types/juce_LV2Common.h View File

@@ -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

+ 183
- 151
source/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp View File

@@ -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);


+ 4
- 0
source/modules/juce_audio_processors/format_types/juce_LV2Resources.h View File

@@ -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

+ 336
- 159
source/modules/juce_audio_processors/format_types/juce_VST3Common.h View File

@@ -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)
};
//==============================================================================


+ 141
- 139
source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp View File

@@ -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);
}


+ 274
- 159
source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat_test.cpp View File

@@ -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);


+ 56
- 35
source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp View File

@@ -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;
}
}


+ 34
- 35
source/modules/juce_audio_processors/juce_audio_processors.cpp View File

@@ -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


+ 1
- 1
source/modules/juce_audio_processors/juce_audio_processors.h View File

@@ -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


+ 0
- 53
source/modules/juce_audio_processors/processors/juce_AudioProcessor.h View File

@@ -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 (&currentHostTime); // 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


+ 0
- 3
source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp View File

@@ -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; }


+ 0
- 3
source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h View File

@@ -50,9 +50,6 @@ protected:
AudioProcessorEditor (AudioProcessor*) noexcept;
public:
// FIXME
virtual void* getPlatformSpecificData();
/** Destructor. */
~AudioProcessorEditor() override;


+ 5
- 19
source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp View File

@@ -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
{


+ 12
- 12
source/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h View File

@@ -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:
//==============================================================================


+ 5
- 5
source/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.h View File

@@ -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)
{


+ 1
- 1
source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp View File

@@ -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);


+ 4
- 4
source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h View File

@@ -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;


+ 5
- 7
source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp View File

@@ -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{});
}
//==============================================================================


+ 1
- 1
source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h View File

@@ -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().


+ 8
- 0
source/modules/juce_audio_processors/utilities/juce_ExtensionsVisitor.h View File

@@ -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. */


+ 41
- 18
source/modules/juce_core/containers/juce_Optional.h View File

@@ -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)
{


+ 476
- 0
source/modules/juce_core/files/juce_AndroidDocument.h View File

@@ -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

+ 7
- 0
source/modules/juce_core/files/juce_File.cpp View File

@@ -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);


+ 6
- 0
source/modules/juce_core/files/juce_File.h View File

@@ -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


source/modules/juce_gui_basics/native/juce_common_MimeTypes.cpp → source/modules/juce_core/files/juce_common_MimeTypes.cpp View File

@@ -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] =
{

source/modules/juce_graphics/native/juce_android_IconHelpers.cpp → source/modules/juce_core/files/juce_common_MimeTypes.h View File

@@ -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

+ 23
- 21
source/modules/juce_core/juce_core.cpp View File

@@ -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


+ 3
- 1
source/modules/juce_core/juce_core.h View File

@@ -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"


+ 2
- 0
source/modules/juce_core/native/juce_BasicNativeHeaders.h View File

@@ -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")


+ 0
- 691
source/modules/juce_core/native/juce_android_Files.cpp View File

@@ -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

+ 0
- 701
source/modules/juce_core/native/juce_android_JNIHelpers.cpp View File

@@ -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 {};
}
}

+ 0
- 1010
source/modules/juce_core/native/juce_android_JNIHelpers.h
File diff suppressed because it is too large
View File


+ 0
- 44
source/modules/juce_core/native/juce_android_Misc.cpp View File

@@ -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

+ 0
- 656
source/modules/juce_core/native/juce_android_Network.cpp View File

@@ -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

+ 0
- 261
source/modules/juce_core/native/juce_android_RuntimePermissions.cpp View File

@@ -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

+ 0
- 241
source/modules/juce_core/native/juce_android_SystemStats.cpp View File

@@ -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

+ 0
- 395
source/modules/juce_core/native/juce_android_Threads.cpp View File

@@ -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, &param);
}
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

+ 1
- 1
source/modules/juce_core/native/juce_mac_ObjCHelpers.h View File

@@ -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;


+ 6
- 0
source/modules/juce_core/native/juce_posix_SharedCode.h View File

@@ -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;


+ 1
- 1
source/modules/juce_core/native/juce_win32_ComSmartPtr.h View File

@@ -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


+ 94
- 9
source/modules/juce_core/native/juce_win32_Files.cpp View File

@@ -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


+ 5
- 4
source/modules/juce_core/network/juce_URL.cpp View File

@@ -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
}
//==============================================================================


+ 2
- 2
source/modules/juce_core/system/juce_CompilerSupport.h View File

@@ -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


+ 8
- 3
source/modules/juce_core/system/juce_StandardHeader.h View File

@@ -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>


+ 1
- 1
source/modules/juce_core/threads/juce_ChildProcess.cpp View File

@@ -81,7 +81,7 @@ String ChildProcess::readAllProcessOutput()
}
uint32 ChildProcess::getPID() const noexcept
int ChildProcess::getPID() const noexcept
{
return activeProcess != nullptr ? activeProcess->getPID() : 0;
}


+ 1
- 1
source/modules/juce_core/threads/juce_ChildProcess.h View File

@@ -101,7 +101,7 @@ public:
*/
bool kill();
uint32 getPID() const noexcept;
int getPID() const noexcept;
private:
//==============================================================================


+ 1
- 1
source/modules/juce_data_structures/juce_data_structures.h View File

@@ -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


+ 4
- 0
source/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp View File

@@ -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();
}


+ 6
- 6
source/modules/juce_events/juce_events.cpp View File

@@ -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


+ 1
- 1
source/modules/juce_events/juce_events.h View File

@@ -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


+ 2
- 0
source/modules/juce_events/messages/juce_MessageManager.cpp View File

@@ -26,6 +26,8 @@ namespace juce
MessageManager::MessageManager() noexcept
: messageThreadId (Thread::getCurrentThreadId())
{
JUCE_VERSION_ID
if (JUCEApplicationBase::isStandaloneApp())
Thread::setCurrentThreadName ("JUCE Message Thread");
}


+ 0
- 302
source/modules/juce_events/native/juce_android_Messaging.cpp View File

@@ -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

+ 0
- 103
source/modules/juce_events/native/juce_ios_MessageManager.mm View File

@@ -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

+ 4
- 4
source/modules/juce_graphics/juce_graphics.cpp View File

@@ -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"


+ 1
- 1
source/modules/juce_graphics/juce_graphics.h View File

@@ -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


+ 0
- 549
source/modules/juce_graphics/native/juce_android_Fonts.cpp View File

@@ -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

+ 0
- 66
source/modules/juce_graphics/native/juce_android_GraphicsContext.cpp View File

@@ -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

+ 3
- 0
source/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTextInterface.h View File

@@ -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;


+ 0
- 1
source/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp View File

@@ -63,7 +63,6 @@ AccessibilityHandler::AccessibilityHandler (Component& comp,
interfaces (std::move (interfacesIn)),
nativeImpl (createNativeImpl (*this))
{
notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementCreated);
}
AccessibilityHandler::~AccessibilityHandler()


+ 21
- 1
source/modules/juce_gui_basics/components/juce_Component.cpp View File

@@ -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();


+ 67
- 62
source/modules/juce_gui_basics/juce_gui_basics.cpp View File

@@ -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

+ 1
- 1
source/modules/juce_gui_basics/juce_gui_basics.h View File

@@ -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


+ 13
- 2
source/modules/juce_gui_basics/layout/juce_Grid.cpp View File

@@ -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)


+ 22
- 5
source/modules/juce_gui_basics/layout/juce_Viewport.cpp View File

@@ -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


+ 11
- 1
source/modules/juce_gui_basics/layout/juce_Viewport.h View File

@@ -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;


+ 3
- 2
source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp View File

@@ -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);
}
//==============================================================================


+ 9
- 3
source/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp View File

@@ -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,


+ 232
- 40
source/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h View File

@@ -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

+ 150
- 125
source/modules/juce_gui_basics/native/accessibility/juce_android_Accessibility.cpp View File

@@ -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 = []


+ 7
- 3
source/modules/juce_gui_basics/native/accessibility/juce_win32_Accessibility.cpp View File

@@ -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;


+ 14
- 0
source/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h View File

@@ -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)


+ 65
- 39
source/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h View File

@@ -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;
});


+ 0
- 900
source/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp View File

@@ -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

+ 0
- 240
source/modules/juce_gui_basics/native/juce_android_FileChooser.cpp View File

@@ -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

+ 0
- 2108
source/modules/juce_gui_basics/native/juce_android_Windowing.cpp
File diff suppressed because it is too large
View File


+ 0
- 211
source/modules/juce_gui_basics/native/juce_ios_ContentSharer.cpp View File

@@ -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

+ 0
- 412
source/modules/juce_gui_basics/native/juce_ios_FileChooser.mm View File

@@ -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

+ 0
- 1400
source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm
File diff suppressed because it is too large
View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save