@@ -1,18 +0,0 @@ | |||
# The JUCE Library | |||
JUCE (Jules' Utility Class Extensions) is an all-encompassing | |||
C++ framework for developing cross-platform software. | |||
It contains pretty much everything you're likely to need to create | |||
most applications, and is particularly well-suited for building | |||
highly-customised GUIs, and for handling graphics and sound. | |||
Most JUCE modules are shared under the GNU Public Licence | |||
(GPLv2, v3, and the AGPLv3). This means that the code can | |||
be freely copied and distributed, and costs nothing to use | |||
in other GPL applications. The juce_audio_basics, | |||
juce_audio_devices, juce_blocks_basics, juce_core and | |||
juce_events modules are permissively licensed under the ISC. | |||
For more information, visit the website: | |||
http://www.juce.com |
@@ -1,14 +0,0 @@ | |||
<hr class="footer"/> | |||
<address class="footer"><small>All content © ROLI Ltd.</small></address><br/> | |||
<script type="text/javascript"> | |||
var _gaq = _gaq || []; | |||
_gaq.push(['_setAccount', 'UA-19759318-1']); | |||
_gaq.push(['_trackPageview']); | |||
(function() { | |||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; | |||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; | |||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); | |||
})(); | |||
</script> | |||
</body> | |||
</html> |
@@ -1,311 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software 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. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
// (For the moment, we'll implement a few local operators for this complex class - one | |||
// day we'll probably either have a juce complex class, or use the C++11 one) | |||
static FFT::Complex operator+ (FFT::Complex a, FFT::Complex b) noexcept { FFT::Complex c = { a.r + b.r, a.i + b.i }; return c; } | |||
static FFT::Complex operator- (FFT::Complex a, FFT::Complex b) noexcept { FFT::Complex c = { a.r - b.r, a.i - b.i }; return c; } | |||
static FFT::Complex operator* (FFT::Complex a, FFT::Complex b) noexcept { FFT::Complex c = { a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r }; return c; } | |||
static FFT::Complex& operator+= (FFT::Complex& a, FFT::Complex b) noexcept { a.r += b.r; a.i += b.i; return a; } | |||
//============================================================================== | |||
struct FFT::FFTConfig | |||
{ | |||
FFTConfig (int sizeOfFFT, bool isInverse) | |||
: fftSize (sizeOfFFT), inverse (isInverse), twiddleTable ((size_t) sizeOfFFT) | |||
{ | |||
for (int i = 0; i < fftSize; ++i) | |||
{ | |||
const double phase = (isInverse ? 2.0 : -2.0) * double_Pi * i / fftSize; | |||
twiddleTable[i].r = (float) cos (phase); | |||
twiddleTable[i].i = (float) sin (phase); | |||
} | |||
const int root = (int) std::sqrt ((double) fftSize); | |||
int divisor = 4, n = fftSize; | |||
for (int i = 0; i < numElementsInArray (factors); ++i) | |||
{ | |||
while ((n % divisor) != 0) | |||
{ | |||
if (divisor == 2) divisor = 3; | |||
else if (divisor == 4) divisor = 2; | |||
else divisor += 2; | |||
if (divisor > root) | |||
divisor = n; | |||
} | |||
n /= divisor; | |||
jassert (divisor == 1 || divisor == 2 || divisor == 4); | |||
factors[i].radix = divisor; | |||
factors[i].length = n; | |||
} | |||
} | |||
void perform (const Complex* input, Complex* output) const noexcept | |||
{ | |||
perform (input, output, 1, 1, factors); | |||
} | |||
const int fftSize; | |||
const bool inverse; | |||
struct Factor { int radix, length; }; | |||
Factor factors[32]; | |||
HeapBlock<Complex> twiddleTable; | |||
void perform (const Complex* input, Complex* output, const int stride, const int strideIn, const Factor* facs) const noexcept | |||
{ | |||
const Factor factor (*facs++); | |||
Complex* const originalOutput = output; | |||
const Complex* const outputEnd = output + factor.radix * factor.length; | |||
if (stride == 1 && factor.radix <= 5) | |||
{ | |||
for (int i = 0; i < factor.radix; ++i) | |||
perform (input + stride * strideIn * i, output + i * factor.length, stride * factor.radix, strideIn, facs); | |||
butterfly (factor, output, stride); | |||
return; | |||
} | |||
if (factor.length == 1) | |||
{ | |||
do | |||
{ | |||
*output++ = *input; | |||
input += stride * strideIn; | |||
} | |||
while (output < outputEnd); | |||
} | |||
else | |||
{ | |||
do | |||
{ | |||
perform (input, output, stride * factor.radix, strideIn, facs); | |||
input += stride * strideIn; | |||
output += factor.length; | |||
} | |||
while (output < outputEnd); | |||
} | |||
butterfly (factor, originalOutput, stride); | |||
} | |||
void butterfly (const Factor factor, Complex* data, const int stride) const noexcept | |||
{ | |||
switch (factor.radix) | |||
{ | |||
case 1: break; | |||
case 2: butterfly2 (data, stride, factor.length); return; | |||
case 4: butterfly4 (data, stride, factor.length); return; | |||
default: jassertfalse; break; | |||
} | |||
Complex* scratch = static_cast<Complex*> (alloca (sizeof (Complex) * (size_t) factor.radix)); | |||
for (int i = 0; i < factor.length; ++i) | |||
{ | |||
for (int k = i, q1 = 0; q1 < factor.radix; ++q1) | |||
{ | |||
scratch[q1] = data[k]; | |||
k += factor.length; | |||
} | |||
for (int k = i, q1 = 0; q1 < factor.radix; ++q1) | |||
{ | |||
int twiddleIndex = 0; | |||
data[k] = scratch[0]; | |||
for (int q = 1; q < factor.radix; ++q) | |||
{ | |||
twiddleIndex += stride * k; | |||
if (twiddleIndex >= fftSize) | |||
twiddleIndex -= fftSize; | |||
data[k] += scratch[q] * twiddleTable[twiddleIndex]; | |||
} | |||
k += factor.length; | |||
} | |||
} | |||
} | |||
void butterfly2 (Complex* data, const int stride, const int length) const noexcept | |||
{ | |||
Complex* dataEnd = data + length; | |||
const Complex* tw = twiddleTable; | |||
for (int i = length; --i >= 0;) | |||
{ | |||
const Complex s (*dataEnd * *tw); | |||
tw += stride; | |||
*dataEnd++ = *data - s; | |||
*data++ += s; | |||
} | |||
} | |||
void butterfly4 (Complex* data, const int stride, const int length) const noexcept | |||
{ | |||
const int lengthX2 = length * 2; | |||
const int lengthX3 = length * 3; | |||
const Complex* twiddle1 = twiddleTable; | |||
const Complex* twiddle2 = twiddle1; | |||
const Complex* twiddle3 = twiddle1; | |||
for (int i = length; --i >= 0;) | |||
{ | |||
const Complex s0 = data[length] * *twiddle1; | |||
const Complex s1 = data[lengthX2] * *twiddle2; | |||
const Complex s2 = data[lengthX3] * *twiddle3; | |||
const Complex s3 = s0 + s2; | |||
const Complex s4 = s0 - s2; | |||
const Complex s5 = *data - s1; | |||
*data += s1; | |||
data[lengthX2] = *data - s3; | |||
twiddle1 += stride; | |||
twiddle2 += stride * 2; | |||
twiddle3 += stride * 3; | |||
*data += s3; | |||
if (inverse) | |||
{ | |||
data[length].r = s5.r - s4.i; | |||
data[length].i = s5.i + s4.r; | |||
data[lengthX3].r = s5.r + s4.i; | |||
data[lengthX3].i = s5.i - s4.r; | |||
} | |||
else | |||
{ | |||
data[length].r = s5.r + s4.i; | |||
data[length].i = s5.i - s4.r; | |||
data[lengthX3].r = s5.r - s4.i; | |||
data[lengthX3].i = s5.i + s4.r; | |||
} | |||
++data; | |||
} | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FFTConfig) | |||
}; | |||
//============================================================================== | |||
FFT::FFT (int order, bool inverse) : config (new FFTConfig (1 << order, inverse)), size (1 << order) {} | |||
FFT::~FFT() {} | |||
void FFT::perform (const Complex* const input, Complex* const output) const noexcept | |||
{ | |||
config->perform (input, output); | |||
} | |||
const size_t maxFFTScratchSpaceToAlloca = 256 * 1024; | |||
void FFT::performRealOnlyForwardTransform (float* d) const noexcept | |||
{ | |||
const size_t scratchSize = 16 + sizeof (FFT::Complex) * (size_t) size; | |||
if (scratchSize < maxFFTScratchSpaceToAlloca) | |||
{ | |||
performRealOnlyForwardTransform (static_cast<Complex*> (alloca (scratchSize)), d); | |||
} | |||
else | |||
{ | |||
HeapBlock<char> heapSpace (scratchSize); | |||
performRealOnlyForwardTransform (reinterpret_cast<Complex*> (heapSpace.getData()), d); | |||
} | |||
} | |||
void FFT::performRealOnlyInverseTransform (float* d) const noexcept | |||
{ | |||
const size_t scratchSize = 16 + sizeof (FFT::Complex) * (size_t) size; | |||
if (scratchSize < maxFFTScratchSpaceToAlloca) | |||
{ | |||
performRealOnlyInverseTransform (static_cast<Complex*> (alloca (scratchSize)), d); | |||
} | |||
else | |||
{ | |||
HeapBlock<char> heapSpace (scratchSize); | |||
performRealOnlyInverseTransform (reinterpret_cast<Complex*> (heapSpace.getData()), d); | |||
} | |||
} | |||
void FFT::performRealOnlyForwardTransform (Complex* scratch, float* d) const noexcept | |||
{ | |||
// This can only be called on an FFT object that was created to do forward transforms. | |||
jassert (! config->inverse); | |||
for (int i = 0; i < size; ++i) | |||
{ | |||
scratch[i].r = d[i]; | |||
scratch[i].i = 0; | |||
} | |||
perform (scratch, reinterpret_cast<Complex*> (d)); | |||
} | |||
void FFT::performRealOnlyInverseTransform (Complex* scratch, float* d) const noexcept | |||
{ | |||
// This can only be called on an FFT object that was created to do inverse transforms. | |||
jassert (config->inverse); | |||
perform (reinterpret_cast<const Complex*> (d), scratch); | |||
const float scaleFactor = 1.0f / size; | |||
for (int i = 0; i < size; ++i) | |||
{ | |||
d[i] = scratch[i].r * scaleFactor; | |||
d[i + size] = scratch[i].i * scaleFactor; | |||
} | |||
} | |||
void FFT::performFrequencyOnlyForwardTransform (float* d) const noexcept | |||
{ | |||
performRealOnlyForwardTransform (d); | |||
const int twiceSize = size * 2; | |||
for (int i = 0; i < twiceSize; i += 2) | |||
{ | |||
d[i / 2] = juce_hypot (d[i], d[i + 1]); | |||
if (i >= size) | |||
{ | |||
d[i] = 0; | |||
d[i + 1] = 0; | |||
} | |||
} | |||
} |
@@ -1,101 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software 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. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
/** | |||
A very minimal FFT class. | |||
This is only a simple low-footprint implementation and isn't tuned for speed - it may | |||
be useful for simple applications where one of the more complex FFT libraries would be | |||
overkill. (But in the future it may end up becoming optimised of course...) | |||
The FFT class itself contains lookup tables, so there's some overhead in creating | |||
one, you should create and cache an FFT object for each size/direction of transform | |||
that you need, and re-use them to perform the actual operation. | |||
*/ | |||
class JUCE_API FFT | |||
{ | |||
public: | |||
/** Initialises an object for performing either a forward or inverse FFT with the given size. | |||
The the number of points the FFT will operate on will be 2 ^ order. | |||
*/ | |||
FFT (int order, bool isInverse); | |||
/** Destructor. */ | |||
~FFT(); | |||
/** A complex number, for the purposes of the FFT class. */ | |||
struct Complex | |||
{ | |||
float r; /**< Real part. */ | |||
float i; /**< Imaginary part. */ | |||
}; | |||
/** Performs an out-of-place FFT, either forward or inverse depending on the mode | |||
that was passed to this object's constructor. | |||
The arrays must contain at least getSize() elements. | |||
*/ | |||
void perform (const Complex* input, Complex* output) const noexcept; | |||
/** Performs an in-place forward transform on a block of real data. | |||
The size of the array passed in must be 2 * getSize(), and the first half | |||
should contain your raw input sample data. On return, the array will contain | |||
complex frequency + phase data, and can be passed to performRealOnlyInverseTransform() | |||
in order to convert it back to reals. | |||
*/ | |||
void performRealOnlyForwardTransform (float* inputOutputData) const noexcept; | |||
/** Performs a reverse operation to data created in performRealOnlyForwardTransform(). | |||
The size of the array passed in must be 2 * getSize(), containing complex | |||
frequency and phase data. On return, the first half of the array will contain | |||
the reconstituted samples. | |||
*/ | |||
void performRealOnlyInverseTransform (float* inputOutputData) const noexcept; | |||
/** Takes an array and simply transforms it to the frequency spectrum. | |||
This may be handy for things like frequency displays or analysis. | |||
*/ | |||
void performFrequencyOnlyForwardTransform (float* inputOutputData) const noexcept; | |||
/** Returns the number of data points that this FFT was created to work with. */ | |||
int getSize() const noexcept { return size; } | |||
private: | |||
JUCE_PUBLIC_IN_DLL_BUILD (struct FFTConfig) | |||
ScopedPointer<FFTConfig> config; | |||
const int size; | |||
void performRealOnlyForwardTransform (Complex*, float*) const noexcept; | |||
void performRealOnlyInverseTransform (Complex*, float*) const noexcept; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FFT) | |||
}; |
@@ -1,387 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#if JUCE_QUICKTIME && ! (JUCE_64BIT || JUCE_IOS) | |||
} // (juce namespace) | |||
#if ! JUCE_WINDOWS | |||
#include <QuickTime/Movies.h> | |||
#include <QuickTime/QTML.h> | |||
#include <QuickTime/QuickTimeComponents.h> | |||
#include <QuickTime/MediaHandlers.h> | |||
#include <QuickTime/ImageCodec.h> | |||
#else | |||
#if JUCE_MSVC | |||
#pragma warning (push) | |||
#pragma warning (disable : 4100) | |||
#endif | |||
/* If you've got an include error here, you probably need to install the QuickTime SDK and | |||
add its header directory to your include path. | |||
Alternatively, if you don't need any QuickTime services, just set the JUCE_QUICKTIME flag to 0. | |||
*/ | |||
#undef SIZE_MAX | |||
#include <Movies.h> | |||
#include <QTML.h> | |||
#include <QuickTimeComponents.h> | |||
#include <MediaHandlers.h> | |||
#include <ImageCodec.h> | |||
#undef SIZE_MAX | |||
#if JUCE_MSVC | |||
#pragma warning (pop) | |||
#endif | |||
#endif | |||
namespace juce | |||
{ | |||
bool juce_OpenQuickTimeMovieFromStream (InputStream* input, Movie& movie, Handle& dataHandle); | |||
static const char* const quickTimeFormatName = "QuickTime file"; | |||
//============================================================================== | |||
class QTAudioReader : public AudioFormatReader | |||
{ | |||
public: | |||
QTAudioReader (InputStream* const input_, const int trackNum_) | |||
: AudioFormatReader (input_, quickTimeFormatName), | |||
ok (false), | |||
movie (0), | |||
trackNum (trackNum_), | |||
lastSampleRead (0), | |||
lastThreadId (0), | |||
extractor (0), | |||
dataHandle (0) | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
{ | |||
bufferList.calloc (256, 1); | |||
#if JUCE_WINDOWS | |||
if (InitializeQTML (0) != noErr) | |||
return; | |||
#endif | |||
if (EnterMovies() != noErr) | |||
return; | |||
bool opened = juce_OpenQuickTimeMovieFromStream (input_, movie, dataHandle); | |||
if (! opened) | |||
return; | |||
{ | |||
const int numTracks = GetMovieTrackCount (movie); | |||
int trackCount = 0; | |||
for (int i = 1; i <= numTracks; ++i) | |||
{ | |||
track = GetMovieIndTrack (movie, i); | |||
media = GetTrackMedia (track); | |||
OSType mediaType; | |||
GetMediaHandlerDescription (media, &mediaType, 0, 0); | |||
if (mediaType == SoundMediaType | |||
&& trackCount++ == trackNum_) | |||
{ | |||
ok = true; | |||
break; | |||
} | |||
} | |||
} | |||
if (! ok) | |||
return; | |||
ok = false; | |||
lengthInSamples = GetMediaDecodeDuration (media); | |||
usesFloatingPointData = false; | |||
samplesPerFrame = (int) (GetMediaDecodeDuration (media) / GetMediaSampleCount (media)); | |||
trackUnitsPerFrame = GetMovieTimeScale (movie) * samplesPerFrame | |||
/ GetMediaTimeScale (media); | |||
MovieAudioExtractionBegin (movie, 0, &extractor); | |||
unsigned long output_layout_size; | |||
OSStatus err = MovieAudioExtractionGetPropertyInfo (extractor, | |||
kQTPropertyClass_MovieAudioExtraction_Audio, | |||
kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, | |||
0, &output_layout_size, 0); | |||
if (err != noErr) | |||
return; | |||
HeapBlock<AudioChannelLayout> qt_audio_channel_layout; | |||
qt_audio_channel_layout.calloc (output_layout_size, 1); | |||
MovieAudioExtractionGetProperty (extractor, | |||
kQTPropertyClass_MovieAudioExtraction_Audio, | |||
kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, | |||
output_layout_size, qt_audio_channel_layout, 0); | |||
qt_audio_channel_layout[0].mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; | |||
MovieAudioExtractionSetProperty (extractor, | |||
kQTPropertyClass_MovieAudioExtraction_Audio, | |||
kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, | |||
output_layout_size, | |||
qt_audio_channel_layout); | |||
err = MovieAudioExtractionGetProperty (extractor, | |||
kQTPropertyClass_MovieAudioExtraction_Audio, | |||
kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, | |||
sizeof (inputStreamDesc), | |||
&inputStreamDesc, 0); | |||
if (err != noErr) | |||
return; | |||
inputStreamDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger | |||
| kAudioFormatFlagIsPacked | |||
| kAudioFormatFlagsNativeEndian; | |||
inputStreamDesc.mBitsPerChannel = sizeof (SInt16) * 8; | |||
inputStreamDesc.mChannelsPerFrame = jmin ((UInt32) 2, inputStreamDesc.mChannelsPerFrame); | |||
inputStreamDesc.mBytesPerFrame = sizeof (SInt16) * inputStreamDesc.mChannelsPerFrame; | |||
inputStreamDesc.mBytesPerPacket = inputStreamDesc.mBytesPerFrame; | |||
err = MovieAudioExtractionSetProperty (extractor, | |||
kQTPropertyClass_MovieAudioExtraction_Audio, | |||
kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, | |||
sizeof (inputStreamDesc), | |||
&inputStreamDesc); | |||
if (err != noErr) | |||
return; | |||
Boolean allChannelsDiscrete = false; | |||
err = MovieAudioExtractionSetProperty (extractor, | |||
kQTPropertyClass_MovieAudioExtraction_Movie, | |||
kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete, | |||
sizeof (allChannelsDiscrete), | |||
&allChannelsDiscrete); | |||
if (err != noErr) | |||
return; | |||
bufferList->mNumberBuffers = 1; | |||
bufferList->mBuffers[0].mNumberChannels = inputStreamDesc.mChannelsPerFrame; | |||
bufferList->mBuffers[0].mDataByteSize = jmax ((UInt32) 4096, (UInt32) (samplesPerFrame * (int) inputStreamDesc.mBytesPerFrame) + 16); | |||
dataBuffer.malloc (bufferList->mBuffers[0].mDataByteSize); | |||
bufferList->mBuffers[0].mData = dataBuffer; | |||
sampleRate = inputStreamDesc.mSampleRate; | |||
bitsPerSample = 16; | |||
numChannels = inputStreamDesc.mChannelsPerFrame; | |||
detachThread(); | |||
ok = true; | |||
} | |||
} | |||
~QTAudioReader() | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
{ | |||
checkThreadIsAttached(); | |||
if (dataHandle != nullptr) | |||
DisposeHandle (dataHandle); | |||
if (extractor != nullptr) | |||
{ | |||
MovieAudioExtractionEnd (extractor); | |||
extractor = nullptr; | |||
} | |||
DisposeMovie (movie); | |||
#if JUCE_MAC | |||
ExitMoviesOnThread(); | |||
#endif | |||
} | |||
} | |||
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
int64 startSampleInFile, int numSamples) | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
{ | |||
checkThreadIsAttached(); | |||
bool readOk = true; | |||
while (numSamples > 0) | |||
{ | |||
if (lastSampleRead != startSampleInFile) | |||
{ | |||
TimeRecord time; | |||
time.scale = (TimeScale) inputStreamDesc.mSampleRate; | |||
time.base = 0; | |||
time.value.hi = 0; | |||
time.value.lo = (UInt32) startSampleInFile; | |||
OSStatus err = MovieAudioExtractionSetProperty (extractor, | |||
kQTPropertyClass_MovieAudioExtraction_Movie, | |||
kQTMovieAudioExtractionMoviePropertyID_CurrentTime, | |||
sizeof (time), &time); | |||
if (err != noErr) | |||
{ | |||
readOk = false; | |||
break; | |||
} | |||
} | |||
int framesToDo = jmin (numSamples, (int) (bufferList->mBuffers[0].mDataByteSize / inputStreamDesc.mBytesPerFrame)); | |||
bufferList->mBuffers[0].mDataByteSize = inputStreamDesc.mBytesPerFrame * (UInt32) framesToDo; | |||
UInt32 outFlags = 0; | |||
UInt32 actualNumFrames = (UInt32) framesToDo; | |||
OSStatus err = MovieAudioExtractionFillBuffer (extractor, &actualNumFrames, bufferList, &outFlags); | |||
if (err != noErr) | |||
{ | |||
readOk = false; | |||
break; | |||
} | |||
lastSampleRead = startSampleInFile + actualNumFrames; | |||
const int samplesReceived = (int) actualNumFrames; | |||
for (int j = numDestChannels; --j >= 0;) | |||
{ | |||
if (destSamples[j] != nullptr) | |||
{ | |||
const short* src = ((const short*) bufferList->mBuffers[0].mData) + j; | |||
for (int i = 0; i < samplesReceived; ++i) | |||
{ | |||
destSamples[j][startOffsetInDestBuffer + i] = (*src << 16); | |||
src += numChannels; | |||
} | |||
} | |||
} | |||
startOffsetInDestBuffer += samplesReceived; | |||
startSampleInFile += samplesReceived; | |||
numSamples -= samplesReceived; | |||
if (((outFlags & kQTMovieAudioExtractionComplete) != 0 || samplesReceived == 0) && numSamples > 0) | |||
{ | |||
for (int j = numDestChannels; --j >= 0;) | |||
if (destSamples[j] != nullptr) | |||
zeromem (destSamples[j] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples); | |||
break; | |||
} | |||
} | |||
detachThread(); | |||
return readOk; | |||
} | |||
} | |||
bool ok; | |||
private: | |||
Movie movie; | |||
Media media; | |||
Track track; | |||
const int trackNum; | |||
double trackUnitsPerFrame; | |||
int samplesPerFrame; | |||
int64 lastSampleRead; | |||
Thread::ThreadID lastThreadId; | |||
MovieAudioExtractionRef extractor; | |||
AudioStreamBasicDescription inputStreamDesc; | |||
HeapBlock<AudioBufferList> bufferList; | |||
HeapBlock<char> dataBuffer; | |||
Handle dataHandle; | |||
//============================================================================== | |||
void checkThreadIsAttached() | |||
{ | |||
#if JUCE_MAC | |||
if (Thread::getCurrentThreadId() != lastThreadId) | |||
EnterMoviesOnThread (0); | |||
AttachMovieToCurrentThread (movie); | |||
#endif | |||
} | |||
void detachThread() | |||
{ | |||
#if JUCE_MAC | |||
DetachMovieFromCurrentThread (movie); | |||
#endif | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (QTAudioReader) | |||
}; | |||
//============================================================================== | |||
QuickTimeAudioFormat::QuickTimeAudioFormat() : AudioFormat (quickTimeFormatName, ".mov .mp3 .mp4 .m4a") | |||
{ | |||
} | |||
QuickTimeAudioFormat::~QuickTimeAudioFormat() | |||
{ | |||
} | |||
Array<int> QuickTimeAudioFormat::getPossibleSampleRates() { return Array<int>(); } | |||
Array<int> QuickTimeAudioFormat::getPossibleBitDepths() { return Array<int>(); } | |||
bool QuickTimeAudioFormat::canDoStereo() { return true; } | |||
bool QuickTimeAudioFormat::canDoMono() { return true; } | |||
//============================================================================== | |||
AudioFormatReader* QuickTimeAudioFormat::createReaderFor (InputStream* sourceStream, | |||
const bool deleteStreamIfOpeningFails) | |||
{ | |||
ScopedPointer<QTAudioReader> r (new QTAudioReader (sourceStream, 0)); | |||
if (r->ok) | |||
return r.release(); | |||
if (! deleteStreamIfOpeningFails) | |||
r->input = 0; | |||
return nullptr; | |||
} | |||
AudioFormatWriter* QuickTimeAudioFormat::createWriterFor (OutputStream* /*streamToWriteTo*/, | |||
double /*sampleRateToUse*/, | |||
unsigned int /*numberOfChannels*/, | |||
int /*bitsPerSample*/, | |||
const StringPairArray& /*metadataValues*/, | |||
int /*qualityOptionIndex*/) | |||
{ | |||
jassertfalse; // not yet implemented! | |||
return nullptr; | |||
} | |||
#endif |
@@ -1,69 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#if JUCE_QUICKTIME | |||
//============================================================================== | |||
/** | |||
Uses QuickTime to read the audio track a movie or media file. | |||
As well as QuickTime movies, this should also manage to open other audio | |||
files that quicktime can understand, like mp3, m4a, etc. | |||
@see AudioFormat | |||
*/ | |||
class JUCE_API QuickTimeAudioFormat : public AudioFormat | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates a format object. */ | |||
QuickTimeAudioFormat(); | |||
/** Destructor. */ | |||
~QuickTimeAudioFormat(); | |||
//============================================================================== | |||
Array<int> getPossibleSampleRates(); | |||
Array<int> getPossibleBitDepths(); | |||
bool canDoStereo(); | |||
bool canDoMono(); | |||
//============================================================================== | |||
AudioFormatReader* createReaderFor (InputStream* sourceStream, | |||
bool deleteStreamIfOpeningFails); | |||
AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, | |||
double sampleRateToUse, | |||
unsigned int numberOfChannels, | |||
int bitsPerSample, | |||
const StringPairArray& metadataValues, | |||
int qualityOptionIndex); | |||
private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (QuickTimeAudioFormat) | |||
}; | |||
#endif |
@@ -1,144 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_AUDIOPLAYHEAD_H_INCLUDED | |||
#define JUCE_AUDIOPLAYHEAD_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
A subclass of AudioPlayHead can supply information about the position and | |||
status of a moving play head during audio playback. | |||
One of these can be supplied to an AudioProcessor object so that it can find | |||
out about the position of the audio that it is rendering. | |||
@see AudioProcessor::setPlayHead, AudioProcessor::getPlayHead | |||
*/ | |||
class JUCE_API AudioPlayHead | |||
{ | |||
protected: | |||
//============================================================================== | |||
AudioPlayHead() {} | |||
public: | |||
virtual ~AudioPlayHead() {} | |||
//============================================================================== | |||
/** Frame rate types. */ | |||
enum FrameRateType | |||
{ | |||
fps24 = 0, | |||
fps25 = 1, | |||
fps2997 = 2, | |||
fps30 = 3, | |||
fps2997drop = 4, | |||
fps30drop = 5, | |||
fpsUnknown = 99 | |||
}; | |||
//============================================================================== | |||
/** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method. | |||
*/ | |||
struct JUCE_API CurrentPositionInfo | |||
{ | |||
/** The tempo in BPM */ | |||
double bpm; | |||
/** Time signature numerator, e.g. the 3 of a 3/4 time sig */ | |||
int timeSigNumerator; | |||
/** Time signature denominator, e.g. the 4 of a 3/4 time sig */ | |||
int timeSigDenominator; | |||
/** The current play position, in samples from the start of the edit. */ | |||
int64 timeInSamples; | |||
/** The current play position, in seconds from the start of the edit. */ | |||
double timeInSeconds; | |||
/** For timecode, the position of the start of the edit, in seconds from 00:00:00:00. */ | |||
double editOriginTime; | |||
/** The current play position, in pulses-per-quarter-note. */ | |||
double ppqPosition; | |||
/** The position of the start of the last bar, in pulses-per-quarter-note. | |||
This is the time from the start of the edit to the start of the current | |||
bar, in ppq units. | |||
Note - this value may be unavailable on some hosts, e.g. Pro-Tools. If | |||
it's not available, the value will be 0. | |||
*/ | |||
double ppqPositionOfLastBarStart; | |||
/** The video frame rate, if applicable. */ | |||
FrameRateType frameRate; | |||
/** True if the transport is currently playing. */ | |||
bool isPlaying; | |||
/** True if the transport is currently recording. | |||
(When isRecording is true, then isPlaying will also be true). | |||
*/ | |||
bool isRecording; | |||
/** The current cycle start position in pulses-per-quarter-note. | |||
Note that not all hosts or plugin formats may provide this value. | |||
@see isLooping | |||
*/ | |||
double ppqLoopStart; | |||
/** The current cycle end position in pulses-per-quarter-note. | |||
Note that not all hosts or plugin formats may provide this value. | |||
@see isLooping | |||
*/ | |||
double ppqLoopEnd; | |||
/** True if the transport is currently looping. */ | |||
bool isLooping; | |||
//============================================================================== | |||
bool operator== (const CurrentPositionInfo& other) const noexcept; | |||
bool operator!= (const CurrentPositionInfo& other) const noexcept; | |||
void resetToDefault(); | |||
}; | |||
//============================================================================== | |||
/** 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. | |||
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 bool getCurrentPosition (CurrentPositionInfo& result) = 0; | |||
}; | |||
#endif // JUCE_AUDIOPLAYHEAD_H_INCLUDED |
@@ -1,56 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software 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. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_SCOPEDXLOCK_H_INCLUDED | |||
#define JUCE_SCOPEDXLOCK_H_INCLUDED | |||
//============================================================================== | |||
#if JUCE_LINUX || DOXYGEN | |||
/** A handy class that uses XLockDisplay and XUnlockDisplay to lock the X server | |||
using RAII (Only available in Linux!). | |||
*/ | |||
class ScopedXLock | |||
{ | |||
public: | |||
/** Creating a ScopedXLock object locks the X display. | |||
This uses XLockDisplay() to grab the display that Juce is using. | |||
*/ | |||
ScopedXLock(); | |||
/** Deleting a ScopedXLock object unlocks the X display. | |||
This calls XUnlockDisplay() to release the lock. | |||
*/ | |||
~ScopedXLock(); | |||
}; | |||
#endif | |||
#endif // JUCE_SCOPEDXLOCK_H_INCLUDED |
@@ -1,153 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2016 - ROLI Ltd. | |||
Permission is granted to use this software 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. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||
OF THIS SOFTWARE. | |||
----------------------------------------------------------------------------- | |||
To release a closed-source product which uses other parts of JUCE not | |||
licensed under the ISC terms, commercial licenses are available: visit | |||
www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
//============================================================================== | |||
class EventsThread : public Thread | |||
{ | |||
public: | |||
EventsThread() | |||
: Thread("EventsThread"), | |||
initializing(false) {} | |||
~EventsThread() | |||
{ | |||
signalThreadShouldExit(); | |||
stopThread(2000); | |||
const ScopedLock sl(queueLock); | |||
jassert(queue.size() == 0); | |||
queue.clear(); | |||
} | |||
bool postMessage(MessageManager::MessageBase* const msg) | |||
{ | |||
const ScopedLock sl(queueLock); | |||
queue.add(msg); | |||
return true; | |||
} | |||
bool isInitializing() const noexcept | |||
{ | |||
return initializing; | |||
} | |||
protected: | |||
void run() override | |||
{ | |||
/* | |||
* We need to know when we're initializing because MessageManager::setCurrentThreadAsMessageThread() | |||
* calls doPlatformSpecificInitialisation/Shutdown, in which we started this thread previously. | |||
* To avoid a deadlock we do not call start/stopThread if still initializing. | |||
*/ | |||
initializing = true; | |||
if (MessageManager* const msgMgr = MessageManager::getInstance()) | |||
msgMgr->setCurrentThreadAsMessageThread(); | |||
initializing = false; | |||
for (; ! threadShouldExit();) | |||
{ | |||
// dispatch messages until no more present, then sleep | |||
for (; dispatchNextInternalMessage();) {} | |||
sleep(25); | |||
} | |||
} | |||
private: | |||
volatile bool initializing; | |||
ReferenceCountedArray<MessageManager::MessageBase> queue; | |||
CriticalSection queueLock; | |||
MessageManager::MessageBase::Ptr popNextMessage() | |||
{ | |||
const ScopedLock sl(queueLock); | |||
return queue.removeAndReturn(0); | |||
} | |||
bool dispatchNextInternalMessage() | |||
{ | |||
if (const MessageManager::MessageBase::Ptr msg = popNextMessage()) | |||
{ | |||
JUCE_TRY | |||
{ | |||
msg->messageCallback(); | |||
return true; | |||
} | |||
JUCE_CATCH_EXCEPTION | |||
} | |||
return false; | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EventsThread) | |||
}; | |||
static EventsThread& getEventsThreadInstance() | |||
{ | |||
static EventsThread eventsThread; | |||
return eventsThread; | |||
} | |||
//============================================================================== | |||
void MessageManager::doPlatformSpecificInitialisation() | |||
{ | |||
EventsThread& eventsThread(getEventsThreadInstance()); | |||
if (! eventsThread.isInitializing()) | |||
eventsThread.startThread(); | |||
} | |||
void MessageManager::doPlatformSpecificShutdown() | |||
{ | |||
EventsThread& eventsThread(getEventsThreadInstance()); | |||
if (! eventsThread.isInitializing()) | |||
eventsThread.stopThread(-1); | |||
} | |||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||
{ | |||
EventsThread& eventsThread(getEventsThreadInstance()); | |||
return eventsThread.postMessage(message); | |||
} | |||
void MessageManager::broadcastMessage (const String& /* value */) | |||
{ | |||
/* TODO */ | |||
} | |||
// this function expects that it will NEVER be called simultaneously for two concurrent threads | |||
bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) | |||
{ | |||
DBG ("MessageManager::dispatchNextMessageOnSystemQueue() unsupported"); | |||
return false; | |||
} |
@@ -1,271 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
extern ::Display* display; | |||
extern ::Window juce_messageWindowHandle; | |||
namespace ClipboardHelpers | |||
{ | |||
static String localClipboardContent; | |||
static Atom atom_UTF8_STRING; | |||
static Atom atom_CLIPBOARD; | |||
static Atom atom_TARGETS; | |||
//============================================================================== | |||
static void initSelectionAtoms() | |||
{ | |||
static bool isInitialised = false; | |||
if (! isInitialised) | |||
{ | |||
isInitialised = true; | |||
atom_UTF8_STRING = XInternAtom (display, "UTF8_STRING", False); | |||
atom_CLIPBOARD = XInternAtom (display, "CLIPBOARD", False); | |||
atom_TARGETS = XInternAtom (display, "TARGETS", False); | |||
} | |||
} | |||
//============================================================================== | |||
// Read the content of a window property as either a locale-dependent string or an utf8 string | |||
// works only for strings shorter than 1000000 bytes | |||
static String readWindowProperty (Window window, Atom prop) | |||
{ | |||
String returnData; | |||
if (display != nullptr) | |||
{ | |||
char* clipData; | |||
Atom actualType; | |||
int actualFormat; | |||
unsigned long numItems, bytesLeft; | |||
if (XGetWindowProperty (display, window, prop, | |||
0L /* offset */, 1000000 /* length (max) */, False, | |||
AnyPropertyType /* format */, | |||
&actualType, &actualFormat, &numItems, &bytesLeft, | |||
(unsigned char**) &clipData) == Success) | |||
{ | |||
if (actualType == atom_UTF8_STRING && actualFormat == 8) | |||
returnData = String::fromUTF8 (clipData, (int) numItems); | |||
else if (actualType == XA_STRING && actualFormat == 8) | |||
returnData = String (clipData, numItems); | |||
if (clipData != nullptr) | |||
XFree (clipData); | |||
jassert (bytesLeft == 0 || numItems == 1000000); | |||
} | |||
XDeleteProperty (display, window, prop); | |||
} | |||
return returnData; | |||
} | |||
//============================================================================== | |||
// Send a SelectionRequest to the window owning the selection and waits for its answer (with a timeout) */ | |||
static bool requestSelectionContent (String& selectionContent, Atom selection, Atom requestedFormat) | |||
{ | |||
Atom property_name = XInternAtom (display, "JUCE_SEL", false); | |||
// The selection owner will be asked to set the JUCE_SEL property on the | |||
// juce_messageWindowHandle with the selection content | |||
XConvertSelection (display, selection, requestedFormat, property_name, | |||
juce_messageWindowHandle, CurrentTime); | |||
int count = 50; // will wait at most for 200 ms | |||
while (--count >= 0) | |||
{ | |||
XEvent event; | |||
if (XCheckTypedWindowEvent (display, juce_messageWindowHandle, SelectionNotify, &event)) | |||
{ | |||
if (event.xselection.property == property_name) | |||
{ | |||
jassert (event.xselection.requestor == juce_messageWindowHandle); | |||
selectionContent = readWindowProperty (event.xselection.requestor, | |||
event.xselection.property); | |||
return true; | |||
} | |||
return false; // the format we asked for was denied.. (event.xselection.property == None) | |||
} | |||
// not very elegant.. we could do a select() or something like that... | |||
// however clipboard content requesting is inherently slow on x11, it | |||
// often takes 50ms or more so... | |||
Thread::sleep (4); | |||
} | |||
return false; | |||
} | |||
//============================================================================== | |||
// Called from the event loop in juce_linux_Messaging in response to SelectionRequest events | |||
static void handleSelection (XSelectionRequestEvent& evt) | |||
{ | |||
if (display != nullptr) | |||
{ | |||
ClipboardHelpers::initSelectionAtoms(); | |||
// the selection content is sent to the target window as a window property | |||
XSelectionEvent reply; | |||
reply.type = SelectionNotify; | |||
reply.display = evt.display; | |||
reply.requestor = evt.requestor; | |||
reply.selection = evt.selection; | |||
reply.target = evt.target; | |||
reply.property = None; // == "fail" | |||
reply.time = evt.time; | |||
HeapBlock<char> data; | |||
int propertyFormat = 0; | |||
size_t numDataItems = 0; | |||
if (evt.selection == XA_PRIMARY || evt.selection == ClipboardHelpers::atom_CLIPBOARD) | |||
{ | |||
if (evt.target == XA_STRING || evt.target == ClipboardHelpers::atom_UTF8_STRING) | |||
{ | |||
// translate to utf8 | |||
numDataItems = ClipboardHelpers::localClipboardContent.getNumBytesAsUTF8() + 1; | |||
data.calloc (numDataItems + 1); | |||
ClipboardHelpers::localClipboardContent.copyToUTF8 (data, numDataItems); | |||
propertyFormat = 8; // bits/item | |||
} | |||
else if (evt.target == ClipboardHelpers::atom_TARGETS) | |||
{ | |||
// another application wants to know what we are able to send | |||
numDataItems = 2; | |||
propertyFormat = 32; // atoms are 32-bit | |||
data.calloc (numDataItems * 4); | |||
Atom* atoms = reinterpret_cast<Atom*> (data.getData()); | |||
atoms[0] = ClipboardHelpers::atom_UTF8_STRING; | |||
atoms[1] = XA_STRING; | |||
evt.target = XA_ATOM; | |||
} | |||
} | |||
else | |||
{ | |||
DBG ("requested unsupported clipboard"); | |||
} | |||
if (data != nullptr) | |||
{ | |||
const size_t maxReasonableSelectionSize = 1000000; | |||
// for very big chunks of data, we should use the "INCR" protocol , which is a pain in the *ss | |||
if (evt.property != None && numDataItems < maxReasonableSelectionSize) | |||
{ | |||
XChangeProperty (evt.display, evt.requestor, | |||
evt.property, evt.target, | |||
propertyFormat /* 8 or 32 */, PropModeReplace, | |||
reinterpret_cast<const unsigned char*> (data.getData()), (int) numDataItems); | |||
reply.property = evt.property; // " == success" | |||
} | |||
} | |||
XSendEvent (evt.display, evt.requestor, 0, NoEventMask, (XEvent*) &reply); | |||
} | |||
} | |||
} | |||
//============================================================================== | |||
typedef void (*SelectionRequestCallback) (XSelectionRequestEvent&); | |||
extern SelectionRequestCallback handleSelectionRequest; | |||
struct ClipboardCallbackInitialiser | |||
{ | |||
ClipboardCallbackInitialiser() | |||
{ | |||
handleSelectionRequest = ClipboardHelpers::handleSelection; | |||
} | |||
}; | |||
static ClipboardCallbackInitialiser clipboardInitialiser; | |||
//============================================================================== | |||
void SystemClipboard::copyTextToClipboard (const String& clipText) | |||
{ | |||
if (display != nullptr) | |||
{ | |||
ClipboardHelpers::initSelectionAtoms(); | |||
ClipboardHelpers::localClipboardContent = clipText; | |||
XSetSelectionOwner (display, XA_PRIMARY, juce_messageWindowHandle, CurrentTime); | |||
XSetSelectionOwner (display, ClipboardHelpers::atom_CLIPBOARD, juce_messageWindowHandle, CurrentTime); | |||
} | |||
} | |||
String SystemClipboard::getTextFromClipboard() | |||
{ | |||
String content; | |||
if (display != nullptr) | |||
{ | |||
ClipboardHelpers::initSelectionAtoms(); | |||
/* 1) try to read from the "CLIPBOARD" selection first (the "high | |||
level" clipboard that is supposed to be filled by ctrl-C | |||
etc). When a clipboard manager is running, the content of this | |||
selection is preserved even when the original selection owner | |||
exits. | |||
2) and then try to read from "PRIMARY" selection (the "legacy" selection | |||
filled by good old x11 apps such as xterm) | |||
*/ | |||
Atom selection = XA_PRIMARY; | |||
Window selectionOwner = None; | |||
if ((selectionOwner = XGetSelectionOwner (display, selection)) == None) | |||
{ | |||
selection = ClipboardHelpers::atom_CLIPBOARD; | |||
selectionOwner = XGetSelectionOwner (display, selection); | |||
} | |||
if (selectionOwner != None) | |||
{ | |||
if (selectionOwner == juce_messageWindowHandle) | |||
{ | |||
content = ClipboardHelpers::localClipboardContent; | |||
} | |||
else | |||
{ | |||
// first try: we want an utf8 string | |||
bool ok = ClipboardHelpers::requestSelectionContent (content, selection, ClipboardHelpers::atom_UTF8_STRING); | |||
if (! ok) | |||
{ | |||
// second chance, ask for a good old locale-dependent string .. | |||
ok = ClipboardHelpers::requestSelectionContent (content, selection, XA_STRING); | |||
} | |||
} | |||
} | |||
} | |||
return content; | |||
} |
@@ -1,142 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
extern ::Display* display; | |||
//============================================================================== | |||
class SystemTrayIconComponent::Pimpl | |||
{ | |||
public: | |||
Pimpl (const Image& im, Window windowH) : image (im) | |||
{ | |||
ScopedXLock xlock; | |||
Screen* const screen = XDefaultScreenOfDisplay (display); | |||
const int screenNumber = XScreenNumberOfScreen (screen); | |||
String screenAtom ("_NET_SYSTEM_TRAY_S"); | |||
screenAtom << screenNumber; | |||
Atom selectionAtom = XInternAtom (display, screenAtom.toUTF8(), false); | |||
XGrabServer (display); | |||
Window managerWin = XGetSelectionOwner (display, selectionAtom); | |||
if (managerWin != None) | |||
XSelectInput (display, managerWin, StructureNotifyMask); | |||
XUngrabServer (display); | |||
XFlush (display); | |||
if (managerWin != None) | |||
{ | |||
XEvent ev = { 0 }; | |||
ev.xclient.type = ClientMessage; | |||
ev.xclient.window = managerWin; | |||
ev.xclient.message_type = XInternAtom (display, "_NET_SYSTEM_TRAY_OPCODE", False); | |||
ev.xclient.format = 32; | |||
ev.xclient.data.l[0] = CurrentTime; | |||
ev.xclient.data.l[1] = 0 /*SYSTEM_TRAY_REQUEST_DOCK*/; | |||
ev.xclient.data.l[2] = (long) windowH; | |||
ev.xclient.data.l[3] = 0; | |||
ev.xclient.data.l[4] = 0; | |||
XSendEvent (display, managerWin, False, NoEventMask, &ev); | |||
XSync (display, False); | |||
} | |||
// For older KDE's ... | |||
long atomData = 1; | |||
Atom trayAtom = XInternAtom (display, "KWM_DOCKWINDOW", false); | |||
XChangeProperty (display, windowH, trayAtom, trayAtom, 32, PropModeReplace, (unsigned char*) &atomData, 1); | |||
// For more recent KDE's... | |||
trayAtom = XInternAtom (display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", false); | |||
XChangeProperty (display, windowH, trayAtom, XA_WINDOW, 32, PropModeReplace, (unsigned char*) &windowH, 1); | |||
// A minimum size must be specified for GNOME and Xfce, otherwise the icon is displayed with a width of 1 | |||
XSizeHints* hints = XAllocSizeHints(); | |||
hints->flags = PMinSize; | |||
hints->min_width = 22; | |||
hints->min_height = 22; | |||
XSetWMNormalHints (display, windowH, hints); | |||
XFree (hints); | |||
} | |||
Image image; | |||
private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||
}; | |||
//============================================================================== | |||
void SystemTrayIconComponent::setIconImage (const Image& newImage) | |||
{ | |||
pimpl = nullptr; | |||
if (newImage.isValid()) | |||
{ | |||
if (! isOnDesktop()) | |||
addToDesktop (0); | |||
pimpl = new Pimpl (newImage, (Window) getWindowHandle()); | |||
setVisible (true); | |||
toFront (false); | |||
} | |||
repaint(); | |||
} | |||
void SystemTrayIconComponent::paint (Graphics& g) | |||
{ | |||
if (pimpl != nullptr) | |||
g.drawImage (pimpl->image, getLocalBounds().toFloat(), | |||
RectanglePlacement::xLeft | RectanglePlacement::yTop | RectanglePlacement::onlyReduceInSize); | |||
} | |||
void SystemTrayIconComponent::setIconTooltip (const String& /*tooltip*/) | |||
{ | |||
// xxx Not implemented! | |||
} | |||
void SystemTrayIconComponent::setHighlighted (bool) | |||
{ | |||
// xxx Not implemented! | |||
} | |||
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/) | |||
{ | |||
// xxx Not implemented! | |||
} | |||
void SystemTrayIconComponent::hideInfoBubble() | |||
{ | |||
// xxx Not implemented! | |||
} | |||
void* SystemTrayIconComponent::getNativeHandle() const | |||
{ | |||
return getWindowHandle(); | |||
} |
@@ -1,122 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
/* | |||
Sorry.. This class isn't implemented on Linux! | |||
*/ | |||
//============================================================================== | |||
WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_) | |||
: browser (0), | |||
blankPageShown (false), | |||
unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_) | |||
{ | |||
// Unfortunately, WebBrowserComponent is not implemented for Linux yet! | |||
// This is just a stub implementation without any useful functionality. | |||
jassertfalse; | |||
setOpaque (true); | |||
} | |||
WebBrowserComponent::~WebBrowserComponent() | |||
{ | |||
} | |||
//============================================================================== | |||
void WebBrowserComponent::goToURL (const String& url, | |||
const StringArray* headers, | |||
const MemoryBlock* postData) | |||
{ | |||
lastURL = url; | |||
if (headers != nullptr) | |||
lastHeaders = *headers; | |||
else | |||
lastHeaders.clear(); | |||
if (postData != nullptr) | |||
lastPostData = *postData; | |||
else | |||
lastPostData.reset(); | |||
blankPageShown = false; | |||
} | |||
void WebBrowserComponent::stop() | |||
{ | |||
} | |||
void WebBrowserComponent::goBack() | |||
{ | |||
lastURL.clear(); | |||
blankPageShown = false; | |||
} | |||
void WebBrowserComponent::goForward() | |||
{ | |||
lastURL.clear(); | |||
} | |||
void WebBrowserComponent::refresh() | |||
{ | |||
} | |||
//============================================================================== | |||
void WebBrowserComponent::paint (Graphics& g) | |||
{ | |||
g.fillAll (Colours::white); | |||
} | |||
void WebBrowserComponent::checkWindowAssociation() | |||
{ | |||
} | |||
void WebBrowserComponent::reloadLastURL() | |||
{ | |||
if (lastURL.isNotEmpty()) | |||
{ | |||
goToURL (lastURL, &lastHeaders, &lastPostData); | |||
lastURL.clear(); | |||
} | |||
} | |||
void WebBrowserComponent::parentHierarchyChanged() | |||
{ | |||
checkWindowAssociation(); | |||
} | |||
void WebBrowserComponent::resized() | |||
{ | |||
} | |||
void WebBrowserComponent::visibilityChanged() | |||
{ | |||
checkWindowAssociation(); | |||
} | |||
void WebBrowserComponent::focusGained (FocusChangeType) | |||
{ | |||
} |
@@ -1,49 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifdef JUCE_TRACKTION_MARKETPLACE_H_INCLUDED | |||
/* When you add this cpp file to your project, you mustn't include it in a file where you've | |||
already included any other headers - just put it inside a file on its own, possibly with your config | |||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||
header files that the compiler may be using. | |||
*/ | |||
#error "Incorrect use of JUCE cpp file" | |||
#endif | |||
#include "AppConfig.h" | |||
#include "juce_tracktion_marketplace.h" | |||
namespace juce | |||
{ | |||
#include "marketplace/juce_OnlineUnlockStatus.cpp" | |||
#if JUCE_MODULE_AVAILABLE_juce_data_structures | |||
#include "marketplace/juce_TracktionMarketplaceStatus.cpp" | |||
#endif | |||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra | |||
#include "marketplace/juce_OnlineUnlockForm.cpp" | |||
#endif | |||
} |
@@ -1,88 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
/******************************************************************************* | |||
The block below describes the properties of this module, and is read by | |||
the Projucer to automatically generate project code that uses it. | |||
For details about the syntax and how to create or use a module, see the | |||
JUCE Module Format.txt file. | |||
BEGIN_JUCE_MODULE_DECLARATION | |||
ID: juce_tracktion_marketplace | |||
vendor: juce | |||
version: 4.3.1 | |||
name: JUCE Tracktion marketplace support | |||
description: Classes for online product authentication via the Tracktion marketplace. | |||
website: http://www.juce.com/juce | |||
license: GPL/Commercial | |||
dependencies: juce_cryptography | |||
END_JUCE_MODULE_DECLARATION | |||
*******************************************************************************/ | |||
#ifndef JUCE_TRACKTION_MARKETPLACE_H_INCLUDED | |||
#define JUCE_TRACKTION_MARKETPLACE_H_INCLUDED | |||
/** | |||
The Tracktion Marketplace module is a simple user-registration system for | |||
allowing you to build apps/plugins with features that are unlocked by a | |||
user having a suitable account on a webserver. | |||
Although originally designed for use with products that are sold on the | |||
Tracktion Marketplace web-store, the module itself is fully open, and can | |||
be used to connect to your own web-store instead, if you implement your | |||
own compatible web-server back-end. | |||
*/ | |||
//============================================================================== | |||
#include <juce_cryptography/juce_cryptography.h> | |||
#if JUCE_MODULE_AVAILABLE_juce_data_structures | |||
#include <juce_data_structures/juce_data_structures.h> | |||
#endif | |||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra | |||
#include <juce_gui_extra/juce_gui_extra.h> | |||
#endif | |||
namespace juce | |||
{ | |||
#if JUCE_MODULE_AVAILABLE_juce_data_structures | |||
#include "marketplace/juce_OnlineUnlockStatus.h" | |||
#include "marketplace/juce_TracktionMarketplaceStatus.h" | |||
#endif | |||
#include "marketplace/juce_KeyFileGeneration.h" | |||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra | |||
#include "marketplace/juce_OnlineUnlockForm.h" | |||
#endif | |||
} | |||
#endif // JUCE_TRACKTION_MARKETPLACE_H_INCLUDED |
@@ -1,107 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
/** | |||
Contains static utilities for generating key-files that can be unlocked by | |||
the OnlineUnlockStatus class. | |||
*/ | |||
class JUCE_API KeyGeneration | |||
{ | |||
public: | |||
/** | |||
Generates the content of a key-file which can be sent to a user's machine to | |||
unlock a product. | |||
The returned value is a block of text containing an RSA-encoded block, followed | |||
by some human-readable details. If you pass this block of text to | |||
OnlineUnlockStatus::applyKeyFile(), it will decrypt it, and if the | |||
key matches and the machine numbers match, it will unlock that machine. | |||
Typically the way you'd use this on a server would be to build a small executable | |||
that simply calls this method and prints the result, so that the webserver can | |||
use this as a reply to the product's auto-registration mechanism. The | |||
keyGenerationAppMain() function is an example of how to build such a function. | |||
@see OnlineUnlockStatus | |||
*/ | |||
static String JUCE_CALLTYPE generateKeyFile (const String& appName, | |||
const String& userEmail, | |||
const String& userName, | |||
const String& machineNumbers, | |||
const RSAKey& privateKey); | |||
/** Similar to the above key file generation method but with an expiry time. | |||
You must supply a Time after which this key file should no longer be considered as active. | |||
N.B. when an app is unlocked with an expiring key file, OnlineUnlockStatus::isUnlocked will | |||
still return false. You must then check OnlineUnlockStatus::getExpiryTime to see if this | |||
expiring key file is still in date and act accordingly. | |||
@see OnlineUnlockStatus | |||
*/ | |||
static String JUCE_CALLTYPE generateExpiringKeyFile (const String& appName, | |||
const String& userEmail, | |||
const String& userName, | |||
const String& machineNumbers, | |||
const Time expiryTime, | |||
const RSAKey& privateKey); | |||
//============================================================================== | |||
/** This is a simple implementation of a key-generator that you could easily wrap in | |||
a command-line main() function for use on your server. | |||
So for example you might use this in a command line app called "unlocker" and | |||
then call it like this: | |||
unlocker MyGreatApp Joe_Bloggs joebloggs@foobar.com 1234abcd,95432ff 22d9aec92d986dd1,923ad49e9e7ff294c | |||
*/ | |||
static inline int keyGenerationAppMain (int argc, char* argv[]) | |||
{ | |||
StringArray args; | |||
for (int i = 1; i < argc; ++i) | |||
args.add (argv[i]); | |||
if (args.size() != 5) | |||
{ | |||
std::cout << "Requires 5 arguments: app-name user-email username machine-numbers private-key" << std::endl | |||
<< " app-name: name of the product being unlocked" << std::endl | |||
<< " user-email: user's email address" << std::endl | |||
<< " username: name of the user. Careful not to allow any spaces!" << std::endl | |||
<< " machine-numbers: a comma- or semicolon-separated list of all machine ID strings this user can run this product on (no whitespace between items!)" << std::endl | |||
<< " private-key: the RSA private key corresponding to the public key you've used in the app" << std::endl | |||
<< std::endl; | |||
return 1; | |||
} | |||
if (! args[4].containsChar (',')) | |||
{ | |||
std::cout << "Not a valid RSA key!" << std::endl; | |||
return 1; | |||
} | |||
std::cout << generateKeyFile (args[0], args[1], args[2], args[3], RSAKey (args[4])) << std::endl; | |||
return 0; | |||
} | |||
}; |
@@ -1,283 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
struct Spinner : public Component, private Timer | |||
{ | |||
Spinner() { startTimer (1000 / 50); } | |||
void timerCallback() override { repaint(); } | |||
void paint (Graphics& g) override | |||
{ | |||
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey, 0, 0, getWidth(), getHeight()); | |||
} | |||
}; | |||
struct OnlineUnlockForm::OverlayComp : public Component, | |||
private Thread, | |||
private Timer | |||
{ | |||
OverlayComp (OnlineUnlockForm& f) : Thread (String()), form (f) | |||
{ | |||
result.succeeded = false; | |||
email = form.emailBox.getText(); | |||
password = form.passwordBox.getText(); | |||
addAndMakeVisible (spinner); | |||
startThread (4); | |||
} | |||
~OverlayComp() | |||
{ | |||
stopThread (10000); | |||
} | |||
void paint (Graphics& g) override | |||
{ | |||
g.fillAll (Colours::white.withAlpha (0.97f)); | |||
g.setColour (Colours::black); | |||
g.setFont (15.0f); | |||
g.drawFittedText (TRANS("Contacting XYZ...").replace ("XYZ", form.status.getWebsiteName()), | |||
getLocalBounds().reduced (20, 0).removeFromTop (proportionOfHeight (0.6f)), | |||
Justification::centred, 5); | |||
} | |||
void resized() override | |||
{ | |||
const int spinnerSize = 40; | |||
spinner.setBounds ((getWidth() - spinnerSize) / 2, proportionOfHeight (0.6f), spinnerSize, spinnerSize); | |||
} | |||
void run() override | |||
{ | |||
result = form.status.attemptWebserverUnlock (email, password); | |||
startTimer (100); | |||
} | |||
void timerCallback() override | |||
{ | |||
spinner.setVisible (false); | |||
stopTimer(); | |||
if (result.errorMessage.isNotEmpty()) | |||
{ | |||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
TRANS("Registration Failed"), | |||
result.errorMessage); | |||
} | |||
else if (result.informativeMessage.isNotEmpty()) | |||
{ | |||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, | |||
TRANS("Registration Complete!"), | |||
result.informativeMessage); | |||
} | |||
else if (result.urlToLaunch.isNotEmpty()) | |||
{ | |||
URL url (result.urlToLaunch); | |||
url.launchInDefaultBrowser(); | |||
} | |||
// (local copies because we're about to delete this) | |||
const bool worked = result.succeeded; | |||
OnlineUnlockForm& f = form; | |||
delete this; | |||
if (worked) | |||
f.dismiss(); | |||
} | |||
OnlineUnlockForm& form; | |||
Spinner spinner; | |||
OnlineUnlockStatus::UnlockResult result; | |||
String email, password; | |||
JUCE_LEAK_DETECTOR (OnlineUnlockForm::OverlayComp) | |||
}; | |||
static juce_wchar getDefaultPasswordChar() noexcept | |||
{ | |||
#if JUCE_LINUX | |||
return 0x2022; | |||
#else | |||
return 0x25cf; | |||
#endif | |||
} | |||
OnlineUnlockForm::OnlineUnlockForm (OnlineUnlockStatus& s, | |||
const String& userInstructions, | |||
bool hasCancelButton) | |||
: message (String(), userInstructions), | |||
passwordBox (String(), getDefaultPasswordChar()), | |||
registerButton (TRANS("Register")), | |||
cancelButton (TRANS ("Cancel")), | |||
status (s) | |||
{ | |||
// Please supply a message to tell your users what to do! | |||
jassert (userInstructions.isNotEmpty()); | |||
setOpaque (true); | |||
emailBox.setText (status.getUserEmail()); | |||
message.setJustificationType (Justification::centred); | |||
addAndMakeVisible (message); | |||
addAndMakeVisible (emailBox); | |||
addAndMakeVisible (passwordBox); | |||
addAndMakeVisible (registerButton); | |||
if (hasCancelButton) | |||
addAndMakeVisible (cancelButton); | |||
emailBox.setEscapeAndReturnKeysConsumed (false); | |||
passwordBox.setEscapeAndReturnKeysConsumed (false); | |||
registerButton.addShortcut (KeyPress (KeyPress::returnKey)); | |||
registerButton.addListener (this); | |||
cancelButton.addListener (this); | |||
lookAndFeelChanged(); | |||
setSize (500, 250); | |||
} | |||
OnlineUnlockForm::~OnlineUnlockForm() | |||
{ | |||
unlockingOverlay.deleteAndZero(); | |||
} | |||
void OnlineUnlockForm::paint (Graphics& g) | |||
{ | |||
g.fillAll (Colours::lightgrey); | |||
} | |||
void OnlineUnlockForm::resized() | |||
{ | |||
/* If you're writing a plugin, then DO NOT USE A POP-UP A DIALOG WINDOW! | |||
Plugins that create external windows are incredibly annoying for users, and | |||
cause all sorts of headaches for hosts. Don't be the person who writes that | |||
plugin that irritates everyone with a nagging dialog box every time they scan! | |||
*/ | |||
jassert (JUCEApplicationBase::isStandaloneApp() || findParentComponentOfClass<DialogWindow>() == nullptr); | |||
const int buttonHeight = 22; | |||
Rectangle<int> r (getLocalBounds().reduced (10, 20)); | |||
Rectangle<int> buttonArea (r.removeFromBottom (buttonHeight)); | |||
registerButton.changeWidthToFitText (buttonHeight); | |||
cancelButton.changeWidthToFitText (buttonHeight); | |||
const int gap = 20; | |||
buttonArea = buttonArea.withSizeKeepingCentre (registerButton.getWidth() | |||
+ (cancelButton.isVisible() ? gap + cancelButton.getWidth() : 0), | |||
buttonHeight); | |||
registerButton.setBounds (buttonArea.removeFromLeft (registerButton.getWidth())); | |||
buttonArea.removeFromLeft (gap); | |||
cancelButton.setBounds (buttonArea); | |||
r.removeFromBottom (20); | |||
// (force use of a default system font to make sure it has the password blob character) | |||
Font font (Font::getDefaultTypefaceForFont (Font (Font::getDefaultSansSerifFontName(), | |||
Font::getDefaultStyle(), | |||
5.0f))); | |||
const int boxHeight = 24; | |||
passwordBox.setBounds (r.removeFromBottom (boxHeight)); | |||
passwordBox.setInputRestrictions (64); | |||
passwordBox.setFont (font); | |||
r.removeFromBottom (20); | |||
emailBox.setBounds (r.removeFromBottom (boxHeight)); | |||
emailBox.setInputRestrictions (512); | |||
emailBox.setFont (font); | |||
r.removeFromBottom (20); | |||
message.setBounds (r); | |||
if (unlockingOverlay != nullptr) | |||
unlockingOverlay->setBounds (getLocalBounds()); | |||
} | |||
void OnlineUnlockForm::lookAndFeelChanged() | |||
{ | |||
Colour labelCol (findColour (TextEditor::backgroundColourId).contrasting (0.5f)); | |||
emailBox.setTextToShowWhenEmpty (TRANS("Email Address"), labelCol); | |||
passwordBox.setTextToShowWhenEmpty (TRANS("Password"), labelCol); | |||
} | |||
void OnlineUnlockForm::showBubbleMessage (const String& text, Component& target) | |||
{ | |||
bubble = new BubbleMessageComponent (500); | |||
addChildComponent (bubble); | |||
AttributedString attString; | |||
attString.append (text, Font (16.0f)); | |||
bubble->showAt (getLocalArea (&target, target.getLocalBounds()), | |||
attString, 500, // numMillisecondsBeforeRemoving | |||
true, // removeWhenMouseClicked | |||
false); // deleteSelfAfterUse | |||
} | |||
void OnlineUnlockForm::buttonClicked (Button* b) | |||
{ | |||
if (b == ®isterButton) | |||
attemptRegistration(); | |||
else if (b == &cancelButton) | |||
dismiss(); | |||
} | |||
void OnlineUnlockForm::attemptRegistration() | |||
{ | |||
if (unlockingOverlay == nullptr) | |||
{ | |||
if (emailBox.getText().trim().length() < 3) | |||
{ | |||
showBubbleMessage (TRANS ("Please enter a valid email address!"), emailBox); | |||
return; | |||
} | |||
if (passwordBox.getText().trim().length() < 3) | |||
{ | |||
showBubbleMessage (TRANS ("Please enter a valid password!"), passwordBox); | |||
return; | |||
} | |||
status.setUserEmail (emailBox.getText()); | |||
addAndMakeVisible (unlockingOverlay = new OverlayComp (*this)); | |||
resized(); | |||
unlockingOverlay->enterModalState(); | |||
} | |||
} | |||
void OnlineUnlockForm::dismiss() | |||
{ | |||
delete this; | |||
} |
@@ -1,88 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
/** Acts as a GUI which asks the user for their details, and calls the approriate | |||
methods on your OnlineUnlockStatus object to attempt to register the app. | |||
You should create one of these components and add it to your parent window, | |||
or use a DialogWindow to display it as a pop-up. But if you're writing a plugin, | |||
then DO NOT USE A DIALOG WINDOW! Add it as a child component of your plugin's editor | |||
component instead. Plugins that pop up external registration windows are incredibly | |||
annoying, and cause all sorts of headaches for hosts. Don't be the person who | |||
writes that plugin that irritates everyone with a dialog box every time they | |||
try to scan for new plugins! | |||
Note that after adding it, you should put the component into a modal state, | |||
and it will automatically delete itself when it has completed. | |||
Although it deletes itself, it's also OK to delete it manually yourself | |||
if you need to get rid of it sooner. | |||
@see OnlineUnlockStatus | |||
*/ | |||
class JUCE_API OnlineUnlockForm : public Component, | |||
private ButtonListener | |||
{ | |||
public: | |||
/** Creates an unlock form that will work with the given status object. | |||
The userInstructions will be displayed above the email and password boxes. | |||
*/ | |||
OnlineUnlockForm (OnlineUnlockStatus&, | |||
const String& userInstructions, | |||
bool hasCancelButton = true); | |||
/** Destructor. */ | |||
~OnlineUnlockForm(); | |||
/** This is called when the form is dismissed (either cancelled or when registration | |||
succeeds). | |||
By default it will delete this, but you can override it to do other things. | |||
*/ | |||
virtual void dismiss(); | |||
/** @internal */ | |||
void paint (Graphics&) override; | |||
/** @internal */ | |||
void resized() override; | |||
/** @internal */ | |||
void lookAndFeelChanged() override; | |||
Label message; | |||
TextEditor emailBox, passwordBox; | |||
TextButton registerButton, cancelButton; | |||
private: | |||
OnlineUnlockStatus& status; | |||
ScopedPointer<BubbleMessageComponent> bubble; | |||
struct OverlayComp; | |||
friend struct OverlayComp; | |||
Component::SafePointer<Component> unlockingOverlay; | |||
void buttonClicked (Button*) override; | |||
void attemptRegistration(); | |||
void showBubbleMessage (const String&, Component&); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnlineUnlockForm) | |||
}; |
@@ -1,496 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
/* Note: there's a bit of light obfuscation in this code, just to make things | |||
a bit more annoying for crackers who try to reverse-engineer your binaries, but | |||
nothing particularly foolproof. | |||
*/ | |||
struct KeyFileUtils | |||
{ | |||
static XmlElement createKeyFileContent (const String& appName, | |||
const String& userEmail, | |||
const String& userName, | |||
const String& machineNumbers, | |||
const String& machineNumbersAttributeName) | |||
{ | |||
XmlElement xml ("key"); | |||
xml.setAttribute ("user", userName); | |||
xml.setAttribute ("email", userEmail); | |||
xml.setAttribute (machineNumbersAttributeName, machineNumbers); | |||
xml.setAttribute ("app", appName); | |||
xml.setAttribute ("date", String::toHexString (Time::getCurrentTime().toMilliseconds())); | |||
return xml; | |||
} | |||
static String createKeyFileComment (const String& appName, | |||
const String& userEmail, | |||
const String& userName, | |||
const String& machineNumbers) | |||
{ | |||
String comment; | |||
comment << "Keyfile for " << appName << newLine; | |||
if (userName.isNotEmpty()) | |||
comment << "User: " << userName << newLine; | |||
comment << "Email: " << userEmail << newLine | |||
<< "Machine numbers: " << machineNumbers << newLine | |||
<< "Created: " << Time::getCurrentTime().toString (true, true); | |||
return comment; | |||
} | |||
//============================================================================== | |||
static String encryptXML (const XmlElement& xml, RSAKey privateKey) | |||
{ | |||
MemoryOutputStream text; | |||
text << xml.createDocument (StringRef(), true); | |||
BigInteger val; | |||
val.loadFromMemoryBlock (text.getMemoryBlock()); | |||
privateKey.applyToValue (val); | |||
return val.toString (16); | |||
} | |||
static String createKeyFile (String comment, | |||
const XmlElement& xml, | |||
RSAKey rsaPrivateKey) | |||
{ | |||
String asHex ("#" + encryptXML (xml, rsaPrivateKey)); | |||
StringArray lines; | |||
lines.add (comment); | |||
lines.add (String()); | |||
const int charsPerLine = 70; | |||
while (asHex.length() > 0) | |||
{ | |||
lines.add (asHex.substring (0, charsPerLine)); | |||
asHex = asHex.substring (charsPerLine); | |||
} | |||
lines.add (String()); | |||
return lines.joinIntoString ("\r\n"); | |||
} | |||
//============================================================================== | |||
static XmlElement decryptXML (String hexData, RSAKey rsaPublicKey) | |||
{ | |||
BigInteger val; | |||
val.parseString (hexData, 16); | |||
RSAKey key (rsaPublicKey); | |||
jassert (key.isValid()); | |||
ScopedPointer<XmlElement> xml; | |||
if (! val.isZero()) | |||
{ | |||
key.applyToValue (val); | |||
const MemoryBlock mb (val.toMemoryBlock()); | |||
if (CharPointer_UTF8::isValidString (static_cast<const char*> (mb.getData()), (int) mb.getSize())) | |||
xml = XmlDocument::parse (mb.toString()); | |||
} | |||
return xml != nullptr ? *xml : XmlElement("key"); | |||
} | |||
static XmlElement getXmlFromKeyFile (String keyFileText, RSAKey rsaPublicKey) | |||
{ | |||
return decryptXML (keyFileText.fromLastOccurrenceOf ("#", false, false).trim(), rsaPublicKey); | |||
} | |||
static StringArray getMachineNumbers (XmlElement xml, StringRef attributeName) | |||
{ | |||
StringArray numbers; | |||
numbers.addTokens (xml.getStringAttribute (attributeName), ",; ", StringRef()); | |||
numbers.trim(); | |||
numbers.removeEmptyStrings(); | |||
return numbers; | |||
} | |||
static String getLicensee (const XmlElement& xml) { return xml.getStringAttribute ("user"); } | |||
static String getEmail (const XmlElement& xml) { return xml.getStringAttribute ("email"); } | |||
static String getAppID (const XmlElement& xml) { return xml.getStringAttribute ("app"); } | |||
struct KeyFileData | |||
{ | |||
String licensee, email, appID; | |||
StringArray machineNumbers; | |||
bool keyFileExpires; | |||
Time expiryTime; | |||
}; | |||
static KeyFileData getDataFromKeyFile (XmlElement xml) | |||
{ | |||
KeyFileData data; | |||
data.licensee = getLicensee (xml); | |||
data.email = getEmail (xml); | |||
data.appID = getAppID (xml); | |||
if (xml.hasAttribute ("expiryTime") && xml.hasAttribute ("expiring_mach")) | |||
{ | |||
data.keyFileExpires = true; | |||
data.machineNumbers.addArray (getMachineNumbers (xml, "expiring_mach")); | |||
data.expiryTime = Time (xml.getStringAttribute ("expiryTime").getHexValue64()); | |||
} | |||
else | |||
{ | |||
data.keyFileExpires = false; | |||
data.machineNumbers.addArray (getMachineNumbers (xml, "mach")); | |||
} | |||
return data; | |||
} | |||
}; | |||
//============================================================================== | |||
#if JUCE_MODULE_AVAILABLE_juce_data_structures | |||
const char* OnlineUnlockStatus::unlockedProp = "u"; | |||
const char* OnlineUnlockStatus::expiryTimeProp = "t"; | |||
static const char* stateTagName = "REG"; | |||
static const char* userNameProp = "user"; | |||
static const char* keyfileDataProp = "key"; | |||
static var machineNumberAllowed (StringArray numbersFromKeyFile, | |||
StringArray localMachineNumbers) | |||
{ | |||
var result; | |||
for (int i = 0; i < localMachineNumbers.size(); ++i) | |||
{ | |||
String localNumber (localMachineNumbers[i].trim()); | |||
if (localNumber.isNotEmpty()) | |||
{ | |||
for (int j = numbersFromKeyFile.size(); --j >= 0;) | |||
{ | |||
var ok (localNumber.trim().equalsIgnoreCase (numbersFromKeyFile[j].trim())); | |||
result.swapWith (ok); | |||
if (result) | |||
break; | |||
} | |||
} | |||
} | |||
return result; | |||
} | |||
//============================================================================== | |||
OnlineUnlockStatus::OnlineUnlockStatus() : status (stateTagName) | |||
{ | |||
} | |||
OnlineUnlockStatus::~OnlineUnlockStatus() | |||
{ | |||
} | |||
void OnlineUnlockStatus::load() | |||
{ | |||
MemoryBlock mb; | |||
mb.fromBase64Encoding (getState()); | |||
if (mb.getSize() > 0) | |||
status = ValueTree::readFromGZIPData (mb.getData(), mb.getSize()); | |||
else | |||
status = ValueTree (stateTagName); | |||
StringArray localMachineNums (getLocalMachineIDs()); | |||
if (machineNumberAllowed (StringArray ("1234"), localMachineNums)) | |||
status.removeProperty (unlockedProp, nullptr); | |||
KeyFileUtils::KeyFileData data; | |||
data = KeyFileUtils::getDataFromKeyFile (KeyFileUtils::getXmlFromKeyFile (status[keyfileDataProp], getPublicKey())); | |||
if (data.keyFileExpires) | |||
{ | |||
if (! doesProductIDMatch (data.appID)) | |||
status.removeProperty (expiryTimeProp, nullptr); | |||
if (! machineNumberAllowed (data.machineNumbers, localMachineNums)) | |||
status.removeProperty (expiryTimeProp, nullptr); | |||
} | |||
else | |||
{ | |||
if (! doesProductIDMatch (data.appID)) | |||
status.removeProperty (unlockedProp, nullptr); | |||
if (! machineNumberAllowed (data.machineNumbers, localMachineNums)) | |||
status.removeProperty (unlockedProp, nullptr); | |||
} | |||
} | |||
void OnlineUnlockStatus::save() | |||
{ | |||
MemoryOutputStream mo; | |||
{ | |||
GZIPCompressorOutputStream gzipStream (&mo, 9); | |||
status.writeToStream (gzipStream); | |||
} | |||
saveState (mo.getMemoryBlock().toBase64Encoding()); | |||
} | |||
char OnlineUnlockStatus::MachineIDUtilities::getPlatformPrefix() | |||
{ | |||
#if JUCE_MAC | |||
return 'M'; | |||
#elif JUCE_WINDOWS | |||
return 'W'; | |||
#elif JUCE_LINUX | |||
return 'L'; | |||
#elif JUCE_IOS | |||
return 'I'; | |||
#elif JUCE_ANDROID | |||
return 'A'; | |||
#endif | |||
} | |||
String OnlineUnlockStatus::MachineIDUtilities::getEncodedIDString (const String& input) | |||
{ | |||
const String platform (String::charToString (getPlatformPrefix())); | |||
return platform + MD5 ((input + "salt_1" + platform).toUTF8()) | |||
.toHexString().substring (0, 9).toUpperCase(); | |||
} | |||
bool OnlineUnlockStatus::MachineIDUtilities::addFileIDToList (StringArray& ids, const File& f) | |||
{ | |||
if (uint64 num = f.getFileIdentifier()) | |||
{ | |||
ids.add (getEncodedIDString (String::toHexString ((int64) num))); | |||
return true; | |||
} | |||
return false; | |||
} | |||
void OnlineUnlockStatus::MachineIDUtilities::addMACAddressesToList (StringArray& ids) | |||
{ | |||
Array<MACAddress> addresses; | |||
MACAddress::findAllAddresses (addresses); | |||
for (int i = 0; i < addresses.size(); ++i) | |||
ids.add (getEncodedIDString (addresses.getReference(i).toString())); | |||
} | |||
StringArray OnlineUnlockStatus::MachineIDUtilities::getLocalMachineIDs() | |||
{ | |||
StringArray ids; | |||
// First choice for an ID number is a filesystem ID for the user's home | |||
// folder or windows directory. | |||
#if JUCE_WINDOWS | |||
MachineIDUtilities::addFileIDToList (ids, File::getSpecialLocation (File::windowsSystemDirectory)); | |||
#else | |||
MachineIDUtilities::addFileIDToList (ids, File ("~")); | |||
#endif | |||
// ..if that fails, use the MAC addresses.. | |||
if (ids.size() == 0) | |||
MachineIDUtilities::addMACAddressesToList (ids); | |||
jassert (ids.size() > 0); // failed to create any IDs! | |||
return ids; | |||
} | |||
StringArray OnlineUnlockStatus::getLocalMachineIDs() | |||
{ | |||
return MachineIDUtilities::getLocalMachineIDs(); | |||
} | |||
void OnlineUnlockStatus::setUserEmail (const String& usernameOrEmail) | |||
{ | |||
status.setProperty (userNameProp, usernameOrEmail, nullptr); | |||
} | |||
String OnlineUnlockStatus::getUserEmail() const | |||
{ | |||
return status[userNameProp].toString(); | |||
} | |||
bool OnlineUnlockStatus::applyKeyFile (String keyFileContent) | |||
{ | |||
KeyFileUtils::KeyFileData data; | |||
data = KeyFileUtils::getDataFromKeyFile (KeyFileUtils::getXmlFromKeyFile (keyFileContent, getPublicKey())); | |||
if (data.licensee.isNotEmpty() && data.email.isNotEmpty() && doesProductIDMatch (data.appID)) | |||
{ | |||
setUserEmail (data.email); | |||
status.setProperty (keyfileDataProp, keyFileContent, nullptr); | |||
status.removeProperty (data.keyFileExpires ? expiryTimeProp : unlockedProp, nullptr); | |||
var actualResult (0), dummyResult (1.0); | |||
var v (machineNumberAllowed (data.machineNumbers, getLocalMachineIDs())); | |||
actualResult.swapWith (v); | |||
v = machineNumberAllowed (StringArray ("01"), getLocalMachineIDs()); | |||
dummyResult.swapWith (v); | |||
jassert (! dummyResult); | |||
if (data.keyFileExpires) | |||
{ | |||
if ((! dummyResult) && actualResult) | |||
status.setProperty (expiryTimeProp, data.expiryTime.toMilliseconds(), nullptr); | |||
return getExpiryTime().toMilliseconds() > 0; | |||
} | |||
if ((! dummyResult) && actualResult) | |||
status.setProperty (unlockedProp, actualResult, nullptr); | |||
return isUnlocked(); | |||
} | |||
return false; | |||
} | |||
static bool canConnectToWebsite (const URL& url) | |||
{ | |||
ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String(), 2000, nullptr)); | |||
return in != nullptr; | |||
} | |||
static bool areMajorWebsitesAvailable() | |||
{ | |||
const char* urlsToTry[] = { "http://google.com", "http://bing.com", "http://amazon.com", | |||
"https://google.com", "https://bing.com", "https://amazon.com", nullptr}; | |||
for (const char** url = urlsToTry; *url != nullptr; ++url) | |||
if (canConnectToWebsite (URL (*url))) | |||
return true; | |||
return false; | |||
} | |||
OnlineUnlockStatus::UnlockResult OnlineUnlockStatus::handleXmlReply (XmlElement xml) | |||
{ | |||
UnlockResult r; | |||
if (const XmlElement* keyNode = xml.getChildByName ("KEY")) | |||
{ | |||
const String keyText (keyNode->getAllSubText().trim()); | |||
r.succeeded = keyText.length() > 10 && applyKeyFile (keyText); | |||
} | |||
else | |||
{ | |||
r.succeeded = false; | |||
} | |||
if (xml.hasTagName ("MESSAGE")) | |||
r.informativeMessage = xml.getStringAttribute ("message").trim(); | |||
if (xml.hasTagName ("ERROR")) | |||
r.errorMessage = xml.getStringAttribute ("error").trim(); | |||
if (xml.getStringAttribute ("url").isNotEmpty()) | |||
r.urlToLaunch = xml.getStringAttribute ("url").trim(); | |||
if (r.errorMessage.isEmpty() && r.informativeMessage.isEmpty() && r.urlToLaunch.isEmpty() && ! r.succeeded) | |||
r.errorMessage = TRANS ("Unexpected or corrupted reply from XYZ").replace ("XYZ", getWebsiteName()) + "...\n\n" | |||
+ TRANS("Please try again in a few minutes, and contact us for support if this message appears again."); | |||
return r; | |||
} | |||
OnlineUnlockStatus::UnlockResult OnlineUnlockStatus::handleFailedConnection() | |||
{ | |||
UnlockResult r; | |||
r.succeeded = false; | |||
r.errorMessage = TRANS("Couldn't connect to XYZ").replace ("XYZ", getWebsiteName()) + "...\n\n"; | |||
if (areMajorWebsitesAvailable()) | |||
r.errorMessage << TRANS("Your internet connection seems to be OK, but our webserver " | |||
"didn't respond... This is most likely a temporary problem, so try " | |||
"again in a few minutes, but if it persists, please contact us for support!"); | |||
else | |||
r.errorMessage << TRANS("No internet sites seem to be accessible from your computer.. Before trying again, " | |||
"please check that your network is working correctly, and make sure " | |||
"that any firewall/security software installed on your machine isn't " | |||
"blocking your web connection."); | |||
return r; | |||
} | |||
OnlineUnlockStatus::UnlockResult OnlineUnlockStatus::attemptWebserverUnlock (const String& email, | |||
const String& password) | |||
{ | |||
// This method will block while it contacts the server, so you must run it on a background thread! | |||
jassert (! MessageManager::getInstance()->isThisTheMessageThread()); | |||
String reply (readReplyFromWebserver (email, password)); | |||
DBG ("Reply from server: " << reply); | |||
ScopedPointer<XmlElement> xml (XmlDocument::parse (reply)); | |||
if (xml != nullptr) | |||
return handleXmlReply (*xml); | |||
return handleFailedConnection(); | |||
} | |||
#endif // JUCE_MODULE_AVAILABLE_juce_data_structures | |||
//============================================================================== | |||
String KeyGeneration::generateKeyFile (const String& appName, | |||
const String& userEmail, | |||
const String& userName, | |||
const String& machineNumbers, | |||
const RSAKey& privateKey) | |||
{ | |||
XmlElement xml (KeyFileUtils::createKeyFileContent (appName, userEmail, userName, machineNumbers, "mach")); | |||
const String comment (KeyFileUtils::createKeyFileComment (appName, userEmail, userName, machineNumbers)); | |||
return KeyFileUtils::createKeyFile (comment, xml, privateKey); | |||
} | |||
String KeyGeneration::generateExpiringKeyFile (const String& appName, | |||
const String& userEmail, | |||
const String& userName, | |||
const String& machineNumbers, | |||
const Time expiryTime, | |||
const RSAKey& privateKey) | |||
{ | |||
XmlElement xml (KeyFileUtils::createKeyFileContent (appName, userEmail, userName, machineNumbers, "expiring_mach")); | |||
xml.setAttribute ("expiryTime", String::toHexString (expiryTime.toMilliseconds())); | |||
String comment (KeyFileUtils::createKeyFileComment (appName, userEmail, userName, machineNumbers)); | |||
comment << newLine << "Expires: " << expiryTime.toString (true, true); | |||
return KeyFileUtils::createKeyFile (comment, xml, privateKey); | |||
} |
@@ -1,258 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
/** | |||
A base class for online unlocking systems. | |||
This class stores information about whether your app has been unlocked for the | |||
current machine, and handles communication with a web-store to perform the | |||
unlock procedure. | |||
You probably won't ever use this base class directly, but rather a store-specific | |||
subclass such as TracktionMarketplaceStatus, which knows how to talk to the particular | |||
online store that you're using. | |||
To use it, you create a subclass which implements all the pure virtual methods | |||
(see their comments to find out what you'll need to make them do). | |||
Then you can create an instance of your subclass which will hold the registration | |||
state. Typically, you'll want to just keep a single instance of the class around for | |||
the duration of your app. You can then call its methods to handle the various | |||
registration tasks. | |||
Areas of your code that need to know whether the user is registered (e.g. to decide | |||
whether a particular feature is available) should call isUnlocked() to find out. | |||
If you want to create a GUI that allows your users to enter their details and | |||
register, see the OnlineUnlockForm class. | |||
@see OnlineUnlockForm, KeyGeneration | |||
*/ | |||
class JUCE_API OnlineUnlockStatus | |||
{ | |||
public: | |||
OnlineUnlockStatus(); | |||
/** Destructor. */ | |||
virtual ~OnlineUnlockStatus(); | |||
//============================================================================== | |||
/** This must return your product's ID, as allocated by the store. */ | |||
virtual String getProductID() = 0; | |||
/** This must check whether a product ID string that the server returned is OK for | |||
unlocking the current app. | |||
*/ | |||
virtual bool doesProductIDMatch (const String& returnedIDFromServer) = 0; | |||
/** This must return the RSA public key for authenticating responses from | |||
the server for this app. You can get this key from your marketplace | |||
account page. | |||
*/ | |||
virtual RSAKey getPublicKey() = 0; | |||
/** This method must store the given string somewhere in your app's | |||
persistent properties, so it can be retrieved later by getState(). | |||
*/ | |||
virtual void saveState (const String&) = 0; | |||
/** This method must retrieve the last state that was provided by the | |||
saveState method. | |||
On first-run, it should just return an empty string. | |||
*/ | |||
virtual String getState() = 0; | |||
/** Returns the name of the web-store website, not for communication, but for | |||
presenting to the user. | |||
*/ | |||
virtual String getWebsiteName() = 0; | |||
/** Returns the URL of the authentication API. */ | |||
virtual URL getServerAuthenticationURL() = 0; | |||
/** Subclasses that talk to a particular web-store will implement this method | |||
to contact their webserver and attempt to unlock the current machine for | |||
the given username and password. The return value is the XML text from the | |||
server which contains error information and/or the encrypted keyfile. | |||
*/ | |||
virtual String readReplyFromWebserver (const String& email, const String& password) = 0; | |||
/** Returns a list of strings, any of which should be unique to this | |||
physical computer. | |||
When testing whether the user is allowed to use the product on this | |||
machine, this list of tokens is compared to the ones that were stored | |||
on the webserver. | |||
The default implementation of this method will simply call | |||
MachineIDUtilities::getLocalMachineIDs(), which provides a default | |||
version of this functionality. | |||
*/ | |||
virtual StringArray getLocalMachineIDs(); | |||
//============================================================================== | |||
// The following methods can be called by your app: | |||
/** Returns true if the product has been successfully authorised for this machine. | |||
The reason it returns a variant rather than a bool is just to make it marginally | |||
more tedious for crackers to work around. Hopefully if this method gets inlined | |||
they'll need to hack all the places where you call it, rather than just the | |||
function itself. | |||
Bear in mind that each place where you check this return value will need to be | |||
changed by a cracker in order to unlock your app, so the more places you call this | |||
method, the more hassle it will be for them to find and crack them all. | |||
*/ | |||
inline var isUnlocked() const { return status[unlockedProp]; } | |||
/** Returns the Time when the keyfile expires. | |||
If a the key file obtained has an expiry time, isUnlocked will return false and this | |||
will return a non-zero time. The interpretation of this is up to your app but could | |||
be used for subscription based models or trial periods. | |||
*/ | |||
inline Time getExpiryTime() const { return Time (static_cast<int64> (status[expiryTimeProp])); } | |||
/** Optionally allows the app to provide the user's email address if | |||
it is known. | |||
You don't need to call this, but if you do it may save the user | |||
typing it in. | |||
*/ | |||
void setUserEmail (const String& usernameOrEmail); | |||
/** Returns the user's email address if known. */ | |||
String getUserEmail() const; | |||
/** Attempts to perform an unlock using a block of key-file data provided. | |||
You may wish to use this as a way of allowing a user to unlock your app | |||
by drag-and-dropping a file containing the key data, or by letting them | |||
select such a file. This is often needed for allowing registration on | |||
machines without internet access. | |||
*/ | |||
bool applyKeyFile (String keyFileContent); | |||
/** This provides some details about the reply that the server gave in a call | |||
to attemptWebserverUnlock(). | |||
*/ | |||
struct UnlockResult | |||
{ | |||
/** If an unlock operation fails, this is the error message that the webserver | |||
supplied (or a message saying that the server couldn't be contacted) | |||
*/ | |||
String errorMessage; | |||
/** This is a message that the webserver returned, and which the user should | |||
be shown. | |||
It's not necessarily an error message, e.g. it might say that there's a | |||
new version of the app available or some other status update. | |||
*/ | |||
String informativeMessage; | |||
/** If the webserver wants the user to be directed to a web-page for further | |||
information, this is the URL that it would like them to go to. | |||
*/ | |||
String urlToLaunch; | |||
/** If the unlock operation succeeded, this will be set to true. */ | |||
bool succeeded; | |||
}; | |||
/** Contacts the webserver and attempts to perform a registration with the | |||
given user details. | |||
The return value will either be a success, or a failure with an error message | |||
from the server, so you should show this message to your user. | |||
Because this method blocks while it contacts the server, you must run it on | |||
a background thread, not on the message thread. For an easier way to create | |||
a GUI to do the unlocking, see OnlineUnlockForm. | |||
*/ | |||
UnlockResult attemptWebserverUnlock (const String& email, const String& password); | |||
/** Attempts to load the status from the state retrieved by getState(). | |||
Call this somewhere in your app's startup code. | |||
*/ | |||
void load(); | |||
/** Triggers a call to saveState which you can use to store the current unlock status | |||
in your app's settings. | |||
*/ | |||
void save(); | |||
/** This class contains some utility functions that might help with machine ID generation. */ | |||
struct MachineIDUtilities | |||
{ | |||
/** Returns a character that represents the current OS. | |||
E.g. 'M' for Mac, 'W' for windows, etc | |||
*/ | |||
static char getPlatformPrefix(); | |||
/** Returns an encoded hash string from the given input string, prefixing it with | |||
a letter to represent the current OS type. | |||
*/ | |||
static String getEncodedIDString (const String& inputString); | |||
/** Utility function that you may want to use in your machine-ID generation code. | |||
This adds an ID string to the given array which is a hash of the filesystem ID of the | |||
given file. | |||
*/ | |||
static bool addFileIDToList (StringArray& result, const File& file); | |||
/** Utility function that you may want to use in your machine-ID generation code. | |||
This adds some ID strings to the given array which represent each MAC address of the machine. | |||
*/ | |||
static void addMACAddressesToList (StringArray& result); | |||
/** This method calculates some machine IDs based on things like network | |||
MAC addresses, hard-disk IDs, etc, but if you want, you can overload | |||
it to generate your own list of IDs. | |||
The IDs that are returned should be short alphanumeric strings | |||
without any punctuation characters. Since users may need to type | |||
them, case is ignored when comparing them. | |||
Note that the first item in the list is considered to be the | |||
"main" ID, and this will be the one that is displayed to the user | |||
and registered with the marketplace webserver. Subsequent IDs are | |||
just used as fallback to avoid false negatives when checking for | |||
registration on machines which have had hardware added/removed | |||
since the product was first registered. | |||
*/ | |||
static StringArray getLocalMachineIDs(); | |||
}; | |||
private: | |||
ValueTree status; | |||
UnlockResult handleXmlReply (XmlElement); | |||
UnlockResult handleFailedConnection(); | |||
static const char* unlockedProp; | |||
static const char* expiryTimeProp; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnlineUnlockStatus) | |||
}; |
@@ -1,54 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
TracktionMarketplaceStatus::TracktionMarketplaceStatus() {} | |||
URL TracktionMarketplaceStatus::getServerAuthenticationURL() | |||
{ | |||
return URL ("https://www.tracktion.com/marketplace/authenticate.php"); | |||
} | |||
String TracktionMarketplaceStatus::getWebsiteName() | |||
{ | |||
return "tracktion.com"; | |||
} | |||
bool TracktionMarketplaceStatus::doesProductIDMatch (const String& returnedIDFromServer) | |||
{ | |||
return getProductID() == returnedIDFromServer; | |||
} | |||
String TracktionMarketplaceStatus::readReplyFromWebserver (const String& email, const String& password) | |||
{ | |||
URL url (getServerAuthenticationURL() | |||
.withParameter ("product", getProductID()) | |||
.withParameter ("email", email) | |||
.withParameter ("pw", password) | |||
.withParameter ("os", SystemStats::getOperatingSystemName()) | |||
.withParameter ("mach", getLocalMachineIDs()[0])); | |||
DBG ("Trying to unlock via URL: " << url.toString (true)); | |||
return url.readEntireTextStream(); | |||
} |
@@ -1,51 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
/** | |||
An implementation of the OnlineUnlockStatus class which talks to the | |||
Tracktion Marketplace server. | |||
For details about how to use this class, see the docs for the base | |||
class: OnlineUnlockStatus. Basically, you need to inherit from it, and | |||
implement all the pure virtual methods to tell it about your product. | |||
@see OnlineUnlockStatus, OnlineUnlockForm, KeyGeneration | |||
*/ | |||
class JUCE_API TracktionMarketplaceStatus : public OnlineUnlockStatus | |||
{ | |||
public: | |||
TracktionMarketplaceStatus(); | |||
/** @internal */ | |||
bool doesProductIDMatch (const String& returnedIDFromServer) override; | |||
/** @internal */ | |||
URL getServerAuthenticationURL() override; | |||
/** @internal */ | |||
String getWebsiteName() override; | |||
/** @internal */ | |||
String readReplyFromWebserver (const String& email, const String& password) override; | |||
private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TracktionMarketplaceStatus) | |||
}; |