| @@ -725,7 +725,7 @@ private: | |||
| const Identifier propertyName (o.getPropertyName(j)); | |||
| String val (o.getProperty (propertyName).toString()); | |||
| if (val.isEmpty() || (val.containsAnyOf (" \t;<>()=,-\r\n") | |||
| if (val.isEmpty() || (val.containsAnyOf (" \t;<>()=,&+-_\r\n") | |||
| && ! (val.trimStart().startsWithChar ('(') | |||
| || val.trimStart().startsWithChar ('{')))) | |||
| val = val.quoted(); | |||
| @@ -233,7 +233,7 @@ public: | |||
| log ("num IDispatch wrapper objs: " + String (--numDOWID)); | |||
| } | |||
| const var getProperty (const Identifier& propertyName) const | |||
| var getProperty (const Identifier& propertyName) const | |||
| { | |||
| const String nameCopy (propertyName.toString()); | |||
| LPCOLESTR name = nameCopy.toUTF16(); | |||
| @@ -312,7 +312,7 @@ public: | |||
| return source->GetIDsOfNames (IID_NULL, (LPOLESTR*) &name, 1, 0, &id) == S_OK; | |||
| } | |||
| const var invokeMethod (const Identifier& methodName, const var* parameters, int numParameters) | |||
| var invokeMethod (const Identifier& methodName, const var* parameters, int numParameters) | |||
| { | |||
| var returnValue; | |||
| const String nameCopy (methodName.toString()); | |||
| @@ -572,7 +572,7 @@ public: | |||
| DBG ("num NP wrapper objs: " + String (--numDOWNP)); | |||
| } | |||
| const var getProperty (const var::identifier& propertyName) const | |||
| var getProperty (const var::identifier& propertyName) const | |||
| { | |||
| NPVariant result; | |||
| VOID_TO_NPVARIANT (result); | |||
| @@ -610,8 +610,8 @@ public: | |||
| return browser.hasmethod (npp, source, getIdentifierFromString (methodName)); | |||
| } | |||
| const var invokeMethod (const var::identifier& methodName, | |||
| const var* parameters, | |||
| var invokeMethod (const var::identifier& methodName, | |||
| const var* parameters, | |||
| int numParameters) | |||
| { | |||
| var returnVal; | |||
| @@ -26751,6 +26751,9 @@ void AudioDeviceManager::createDeviceTypesIfNeeded() | |||
| if (availableDeviceTypes.size() > 0) | |||
| currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | |||
| for (int i = 0; i < availableDeviceTypes.size(); ++i) | |||
| availableDeviceTypes.getUnchecked(i)->addListener (&callbackHandler); | |||
| } | |||
| } | |||
| @@ -26760,6 +26763,11 @@ const OwnedArray <AudioIODeviceType>& AudioDeviceManager::getAvailableDeviceType | |||
| return availableDeviceTypes; | |||
| } | |||
| void AudioDeviceManager::audioDeviceListChanged() | |||
| { | |||
| sendChangeMessage(); | |||
| } | |||
| static void addIfNotNull (OwnedArray <AudioIODeviceType>& list, AudioIODeviceType* const device) | |||
| { | |||
| if (device != nullptr) | |||
| @@ -27528,6 +27536,11 @@ void AudioDeviceManager::CallbackHandler::handleIncomingMidiMessage (MidiInput* | |||
| owner->handleIncomingMidiMessageInt (source, message); | |||
| } | |||
| void AudioDeviceManager::CallbackHandler::audioDeviceListChanged() | |||
| { | |||
| owner->audioDeviceListChanged(); | |||
| } | |||
| void AudioDeviceManager::playTestSound() | |||
| { | |||
| { // cunningly nested to swap, unlock and delete in that order. | |||
| @@ -27632,6 +27645,14 @@ AudioIODeviceType::~AudioIODeviceType() | |||
| { | |||
| } | |||
| void AudioIODeviceType::addListener (Listener* l) { listeners.add (l); } | |||
| void AudioIODeviceType::removeListener (Listener* l) { listeners.remove (l); } | |||
| void AudioIODeviceType::callDeviceChangeListeners() | |||
| { | |||
| listeners.call (&AudioIODeviceType::Listener::audioDeviceListChanged); | |||
| } | |||
| #if ! JUCE_MAC | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; } | |||
| #endif | |||
| @@ -55142,14 +55163,18 @@ void TextEditor::setText (const String& newText, | |||
| } | |||
| } | |||
| Value& TextEditor::getTextValue() | |||
| void TextEditor::updateValueFromText() | |||
| { | |||
| if (valueTextNeedsUpdating) | |||
| { | |||
| valueTextNeedsUpdating = false; | |||
| textValue = getText(); | |||
| } | |||
| } | |||
| Value& TextEditor::getTextValue() | |||
| { | |||
| updateValueFromText(); | |||
| return textValue; | |||
| } | |||
| @@ -56082,6 +56107,7 @@ void TextEditor::handleCommandMessage (const int commandId) | |||
| break; | |||
| case TextEditorDefs::focusLossMessageId: | |||
| updateValueFromText(); | |||
| listeners.callChecked (checker, &TextEditorListener::textEditorFocusLost, (TextEditor&) *this); | |||
| break; | |||
| @@ -75025,6 +75051,14 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInputSelectorComponentListBox); | |||
| }; | |||
| struct AudioDeviceSetupDetails | |||
| { | |||
| AudioDeviceManager* manager; | |||
| int minNumInputChannels, maxNumInputChannels; | |||
| int minNumOutputChannels, maxNumOutputChannels; | |||
| bool useStereoPairs; | |||
| }; | |||
| class AudioDeviceSettingsPanel : public Component, | |||
| public ChangeListener, | |||
| public ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug) | |||
| @@ -75032,7 +75066,7 @@ class AudioDeviceSettingsPanel : public Component, | |||
| { | |||
| public: | |||
| AudioDeviceSettingsPanel (AudioIODeviceType* type_, | |||
| AudioIODeviceType::DeviceSetupDetails& setup_, | |||
| AudioDeviceSetupDetails& setup_, | |||
| const bool hideAdvancedOptionsWithButton) | |||
| : type (type_), | |||
| setup (setup_) | |||
| @@ -75302,7 +75336,7 @@ public: | |||
| private: | |||
| AudioIODeviceType* const type; | |||
| const AudioIODeviceType::DeviceSetupDetails setup; | |||
| const AudioDeviceSetupDetails setup; | |||
| ScopedPointer<ComboBox> outputDeviceDropDown, inputDeviceDropDown, sampleRateDropDown, bufferSizeDropDown; | |||
| ScopedPointer<Label> outputDeviceLabel, inputDeviceLabel, sampleRateLabel, bufferSizeLabel, inputChanLabel, outputChanLabel; | |||
| @@ -75487,7 +75521,7 @@ public: | |||
| audioOutputType | |||
| }; | |||
| ChannelSelectorListBox (const AudioIODeviceType::DeviceSetupDetails& setup_, | |||
| ChannelSelectorListBox (const AudioDeviceSetupDetails& setup_, | |||
| const BoxType type_, | |||
| const String& noItemsMessage_) | |||
| : ListBox (String::empty, nullptr), | |||
| @@ -75630,7 +75664,7 @@ public: | |||
| private: | |||
| const AudioIODeviceType::DeviceSetupDetails setup; | |||
| const AudioDeviceSetupDetails setup; | |||
| const BoxType type; | |||
| const String noItemsMessage; | |||
| StringArray items; | |||
| @@ -75900,7 +75934,7 @@ void AudioDeviceSelectorComponent::updateAllControls() | |||
| if (type != nullptr) | |||
| { | |||
| AudioIODeviceType::DeviceSetupDetails details; | |||
| AudioDeviceSetupDetails details; | |||
| details.manager = &deviceManager; | |||
| details.minNumInputChannels = minInputChannels; | |||
| details.maxNumInputChannels = maxInputChannels; | |||
| @@ -246925,13 +246959,52 @@ void PlatformUtilities::beep() | |||
| // compiled on its own). | |||
| #if JUCE_INCLUDED_FILE | |||
| class HiddenMessageWindow | |||
| { | |||
| public: | |||
| HiddenMessageWindow (const TCHAR* const messageWindowName, WNDPROC wndProc) | |||
| { | |||
| String className ("JUCE_"); | |||
| className << String::toHexString (Time::getHighResolutionTicks()); | |||
| HMODULE moduleHandle = (HMODULE) PlatformUtilities::getCurrentModuleInstanceHandle(); | |||
| WNDCLASSEX wc = { 0 }; | |||
| wc.cbSize = sizeof (wc); | |||
| wc.lpfnWndProc = wndProc; | |||
| wc.cbWndExtra = 4; | |||
| wc.hInstance = moduleHandle; | |||
| wc.lpszClassName = className.toWideCharPointer(); | |||
| atom = RegisterClassEx (&wc); | |||
| jassert (atom != 0); | |||
| hwnd = CreateWindow (getClassNameFromAtom(), messageWindowName, | |||
| 0, 0, 0, 0, 0, 0, 0, moduleHandle, 0); | |||
| jassert (hwnd != 0); | |||
| } | |||
| ~HiddenMessageWindow() | |||
| { | |||
| DestroyWindow (hwnd); | |||
| UnregisterClass (getClassNameFromAtom(), 0); | |||
| } | |||
| inline HWND getHWND() const noexcept { return hwnd; } | |||
| private: | |||
| ATOM atom; | |||
| HWND hwnd; | |||
| LPCTSTR getClassNameFromAtom() noexcept { return (LPCTSTR) MAKELONG (atom, 0); } | |||
| }; | |||
| static const unsigned int specialId = WM_APP + 0x4400; | |||
| static const unsigned int broadcastId = WM_APP + 0x4403; | |||
| static const unsigned int specialCallbackId = WM_APP + 0x4402; | |||
| static const TCHAR* const messageWindowName = _T("JUCEWindow"); | |||
| static ATOM messageWindowClassAtom = 0; | |||
| static LPCTSTR getMessageWindowClassName() noexcept { return (LPCTSTR) MAKELONG (messageWindowClassAtom, 0); } | |||
| static const TCHAR messageWindowName[] = _T("JUCEWindow"); | |||
| static ScopedPointer<HiddenMessageWindow> messageWindow; | |||
| HWND juce_messageWindowHandle = 0; | |||
| @@ -247149,34 +247222,14 @@ void MessageManager::doPlatformSpecificInitialisation() | |||
| { | |||
| OleInitialize (0); | |||
| // this name has to be different for each app/dll instance because otherwise | |||
| // poor old Win32 can get a bit confused (even despite it not being a process-global | |||
| // window class). | |||
| String className ("JUCEcs_"); | |||
| className << (int) (Time::getHighResolutionTicks() & 0x7fffffff); | |||
| HMODULE moduleHandle = (HMODULE) PlatformUtilities::getCurrentModuleInstanceHandle(); | |||
| WNDCLASSEX wc = { 0 }; | |||
| wc.cbSize = sizeof (wc); | |||
| wc.lpfnWndProc = (WNDPROC) juce_MessageWndProc; | |||
| wc.cbWndExtra = 4; | |||
| wc.hInstance = moduleHandle; | |||
| wc.lpszClassName = className.toWideCharPointer(); | |||
| messageWindowClassAtom = RegisterClassEx (&wc); | |||
| jassert (messageWindowClassAtom != 0); | |||
| juce_messageWindowHandle = CreateWindow (getMessageWindowClassName(), messageWindowName, | |||
| 0, 0, 0, 0, 0, 0, 0, moduleHandle, 0); | |||
| jassert (juce_messageWindowHandle != 0); | |||
| messageWindow = new HiddenMessageWindow (messageWindowName, (WNDPROC) juce_MessageWndProc); | |||
| juce_messageWindowHandle = messageWindow->getHWND(); | |||
| } | |||
| void MessageManager::doPlatformSpecificShutdown() | |||
| { | |||
| DestroyWindow (juce_messageWindowHandle); | |||
| UnregisterClass (getMessageWindowClassName(), 0); | |||
| messageWindow = nullptr; | |||
| OleUninitialize(); | |||
| } | |||
| @@ -260473,8 +260526,6 @@ public: | |||
| bool initialise() | |||
| { | |||
| double defaultSampleRateIn = 0, defaultSampleRateOut = 0; | |||
| int minBufferSizeIn = 0, defaultBufferSizeIn = 0, minBufferSizeOut = 0, defaultBufferSizeOut = 0; | |||
| latencyIn = latencyOut = 0; | |||
| Array <double> ratesIn, ratesOut; | |||
| @@ -260800,12 +260851,10 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType | |||
| public: | |||
| WASAPIAudioIODeviceType() | |||
| : AudioIODeviceType ("Windows Audio"), | |||
| deviceChangeCatcher (_T("Windows Audio"), (WNDPROC) deviceChangeEventCallback), | |||
| hasScanned (false) | |||
| { | |||
| } | |||
| ~WASAPIAudioIODeviceType() | |||
| { | |||
| SetWindowLongPtr (deviceChangeCatcher.getHWND(), GWLP_USERDATA, (LONG_PTR) this); | |||
| } | |||
| void scanForDevices() | |||
| @@ -260817,68 +260866,8 @@ public: | |||
| outputDeviceIds.clear(); | |||
| inputDeviceIds.clear(); | |||
| ComSmartPtr <IMMDeviceEnumerator> enumerator; | |||
| if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) | |||
| return; | |||
| const String defaultRenderer = getDefaultEndpoint (enumerator, false); | |||
| const String defaultCapture = getDefaultEndpoint (enumerator, true); | |||
| ComSmartPtr <IMMDeviceCollection> deviceCollection; | |||
| UINT32 numDevices = 0; | |||
| if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())) | |||
| && check (deviceCollection->GetCount (&numDevices)))) | |||
| return; | |||
| for (UINT32 i = 0; i < numDevices; ++i) | |||
| { | |||
| ComSmartPtr <IMMDevice> device; | |||
| if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress()))) | |||
| continue; | |||
| const String deviceId (getDeviceID (device)); | |||
| DWORD state = 0; | |||
| if (! check (device->GetState (&state))) | |||
| continue; | |||
| if (state != DEVICE_STATE_ACTIVE) | |||
| continue; | |||
| String name; | |||
| { | |||
| ComSmartPtr <IPropertyStore> properties; | |||
| if (! check (device->OpenPropertyStore (STGM_READ, properties.resetAndGetPointerAddress()))) | |||
| continue; | |||
| PROPVARIANT value; | |||
| PropVariantInit (&value); | |||
| if (check (properties->GetValue (PKEY_Device_FriendlyName, &value))) | |||
| name = value.pwszVal; | |||
| PropVariantClear (&value); | |||
| } | |||
| const EDataFlow flow = getDataFlow (device); | |||
| if (flow == eRender) | |||
| { | |||
| const int index = (deviceId == defaultRenderer) ? 0 : -1; | |||
| outputDeviceIds.insert (index, deviceId); | |||
| outputDeviceNames.insert (index, name); | |||
| } | |||
| else if (flow == eCapture) | |||
| { | |||
| const int index = (deviceId == defaultCapture) ? 0 : -1; | |||
| inputDeviceIds.insert (index, deviceId); | |||
| inputDeviceNames.insert (index, name); | |||
| } | |||
| } | |||
| inputDeviceNames.appendNumbersToDuplicates (false, false); | |||
| outputDeviceNames.appendNumbersToDuplicates (false, false); | |||
| scan (outputDeviceNames, inputDeviceNames, | |||
| outputDeviceIds, inputDeviceIds); | |||
| } | |||
| StringArray getDeviceNames (bool wantInputNames) const | |||
| @@ -260935,6 +260924,7 @@ public: | |||
| StringArray inputDeviceNames, inputDeviceIds; | |||
| private: | |||
| HiddenMessageWindow deviceChangeCatcher; | |||
| bool hasScanned; | |||
| static String getDefaultEndpoint (IMMDeviceEnumerator* const enumerator, const bool forCapture) | |||
| @@ -260947,7 +260937,7 @@ private: | |||
| WCHAR* deviceId = nullptr; | |||
| if (check (dev->GetId (&deviceId))) | |||
| { | |||
| s = String (deviceId); | |||
| s = deviceId; | |||
| CoTaskMemFree (deviceId); | |||
| } | |||
| @@ -260957,6 +260947,104 @@ private: | |||
| return s; | |||
| } | |||
| void scan (StringArray& outputDeviceNames, | |||
| StringArray& inputDeviceNames, | |||
| StringArray& outputDeviceIds, | |||
| StringArray& inputDeviceIds) | |||
| { | |||
| ComSmartPtr <IMMDeviceEnumerator> enumerator; | |||
| if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) | |||
| return; | |||
| const String defaultRenderer (getDefaultEndpoint (enumerator, false)); | |||
| const String defaultCapture (getDefaultEndpoint (enumerator, true)); | |||
| ComSmartPtr <IMMDeviceCollection> deviceCollection; | |||
| UINT32 numDevices = 0; | |||
| if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())) | |||
| && check (deviceCollection->GetCount (&numDevices)))) | |||
| return; | |||
| for (UINT32 i = 0; i < numDevices; ++i) | |||
| { | |||
| ComSmartPtr <IMMDevice> device; | |||
| if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress()))) | |||
| continue; | |||
| DWORD state = 0; | |||
| if (! (check (device->GetState (&state)) && state == DEVICE_STATE_ACTIVE)) | |||
| continue; | |||
| const String deviceId (getDeviceID (device)); | |||
| String name; | |||
| { | |||
| ComSmartPtr <IPropertyStore> properties; | |||
| if (! check (device->OpenPropertyStore (STGM_READ, properties.resetAndGetPointerAddress()))) | |||
| continue; | |||
| PROPVARIANT value; | |||
| PropVariantInit (&value); | |||
| if (check (properties->GetValue (PKEY_Device_FriendlyName, &value))) | |||
| name = value.pwszVal; | |||
| PropVariantClear (&value); | |||
| } | |||
| const EDataFlow flow = getDataFlow (device); | |||
| if (flow == eRender) | |||
| { | |||
| const int index = (deviceId == defaultRenderer) ? 0 : -1; | |||
| outputDeviceIds.insert (index, deviceId); | |||
| outputDeviceNames.insert (index, name); | |||
| } | |||
| else if (flow == eCapture) | |||
| { | |||
| const int index = (deviceId == defaultCapture) ? 0 : -1; | |||
| inputDeviceIds.insert (index, deviceId); | |||
| inputDeviceNames.insert (index, name); | |||
| } | |||
| } | |||
| inputDeviceNames.appendNumbersToDuplicates (false, false); | |||
| outputDeviceNames.appendNumbersToDuplicates (false, false); | |||
| } | |||
| static LRESULT CALLBACK deviceChangeEventCallback (HWND h, const UINT message, | |||
| const WPARAM wParam, const LPARAM lParam) | |||
| { | |||
| if (message == WM_DEVICECHANGE | |||
| && (wParam == 0x8000 /*DBT_DEVICEARRIVAL*/ | |||
| || wParam == 0x8004 /*DBT_DEVICEREMOVECOMPLETE*/)) | |||
| { | |||
| ((WASAPIAudioIODeviceType*) GetWindowLongPtr (h, GWLP_USERDATA))->handleDeviceChange(); | |||
| } | |||
| return DefWindowProc (h, message, wParam, lParam); | |||
| } | |||
| void handleDeviceChange() | |||
| { | |||
| StringArray newOutNames, newInNames, newOutIds, newInIds; | |||
| scan (newOutNames, newInNames, newOutIds, newInIds); | |||
| if (newOutNames != outputDeviceNames | |||
| || newInNames != inputDeviceNames | |||
| || newOutIds != outputDeviceIds | |||
| || newInIds != inputDeviceIds) | |||
| { | |||
| hasScanned = true; | |||
| outputDeviceNames = newOutNames; | |||
| inputDeviceNames = newInNames; | |||
| outputDeviceIds = newOutIds; | |||
| inputDeviceIds = newInIds; | |||
| callDeviceChangeListeners(); | |||
| } | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType); | |||
| }; | |||
| @@ -277275,10 +277363,10 @@ public: | |||
| view.frame = CGRectMake ((CGFloat) bounds.getX(), (CGFloat) bounds.getY(), | |||
| (CGFloat) bounds.getWidth(), (CGFloat) bounds.getHeight()); | |||
| if (lastWidth != w || lastHeight != h) | |||
| if (lastWidth != bounds.getWidth() || lastHeight != bounds.getHeight()) | |||
| { | |||
| lastWidth = w; | |||
| lastHeight = h; | |||
| lastWidth = bounds.getWidth(); | |||
| lastHeight = bounds.getHeight(); | |||
| freeGLBuffers(); | |||
| createGLBuffers(); | |||
| } | |||
| @@ -280726,6 +280814,7 @@ public: | |||
| virtual void drawRect (NSRect r); | |||
| virtual bool canBecomeKeyWindow(); | |||
| virtual void becomeKeyWindow(); | |||
| virtual bool windowShouldClose(); | |||
| virtual void redirectMovedOrResized(); | |||
| @@ -281186,7 +281275,7 @@ END_JUCE_NAMESPACE | |||
| [super becomeKeyWindow]; | |||
| if (owner != nullptr) | |||
| owner->grabFocus(); | |||
| owner->becomeKeyWindow(); | |||
| } | |||
| - (BOOL) windowShouldClose: (id) window | |||
| @@ -282097,6 +282186,12 @@ bool NSViewComponentPeer::canBecomeKeyWindow() | |||
| return (getStyleFlags() & JUCE_NAMESPACE::ComponentPeer::windowIgnoresKeyPresses) == 0; | |||
| } | |||
| void NSViewComponentPeer::becomeKeyWindow() | |||
| { | |||
| handleBroughtToFront(); | |||
| grabFocus(); | |||
| } | |||
| bool NSViewComponentPeer::windowShouldClose() | |||
| { | |||
| if (! isValidPeer (this)) | |||
| @@ -283188,10 +283283,10 @@ public: | |||
| view.frame = CGRectMake ((CGFloat) bounds.getX(), (CGFloat) bounds.getY(), | |||
| (CGFloat) bounds.getWidth(), (CGFloat) bounds.getHeight()); | |||
| if (lastWidth != w || lastHeight != h) | |||
| if (lastWidth != bounds.getWidth() || lastHeight != bounds.getHeight()) | |||
| { | |||
| lastWidth = w; | |||
| lastHeight = h; | |||
| lastWidth = bounds.getWidth(); | |||
| lastHeight = bounds.getHeight(); | |||
| freeGLBuffers(); | |||
| createGLBuffers(); | |||
| } | |||
| @@ -286984,6 +287079,22 @@ public: | |||
| : AudioIODeviceType ("CoreAudio"), | |||
| hasScanned (false) | |||
| { | |||
| AudioObjectPropertyAddress pa; | |||
| pa.mSelector = kAudioHardwarePropertyDevices; | |||
| pa.mScope = kAudioObjectPropertyScopeWildcard; | |||
| pa.mElement = kAudioObjectPropertyElementWildcard; | |||
| AudioObjectAddPropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, this); | |||
| } | |||
| ~CoreAudioIODeviceType() | |||
| { | |||
| AudioObjectPropertyAddress pa; | |||
| pa.mSelector = kAudioHardwarePropertyDevices; | |||
| pa.mScope = kAudioObjectPropertyScopeWildcard; | |||
| pa.mElement = kAudioObjectPropertyElementWildcard; | |||
| AudioObjectRemovePropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, this); | |||
| } | |||
| void scanForDevices() | |||
| @@ -287156,6 +287267,18 @@ private: | |||
| return total; | |||
| } | |||
| void audioDeviceListChanged() | |||
| { | |||
| scanForDevices(); | |||
| callDeviceChangeListeners(); | |||
| } | |||
| static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) | |||
| { | |||
| static_cast <CoreAudioIODeviceType*> (clientData)->audioDeviceListChanged(); | |||
| return noErr; | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType); | |||
| }; | |||
| @@ -73,7 +73,7 @@ namespace JuceDummyNamespace {} | |||
| */ | |||
| #define JUCE_MAJOR_VERSION 1 | |||
| #define JUCE_MINOR_VERSION 53 | |||
| #define JUCE_BUILDNUMBER 104 | |||
| #define JUCE_BUILDNUMBER 105 | |||
| /** Current Juce version number. | |||
| @@ -39887,14 +39887,32 @@ public: | |||
| virtual AudioIODevice* createDevice (const String& outputDeviceName, | |||
| const String& inputDeviceName) = 0; | |||
| struct DeviceSetupDetails | |||
| /** | |||
| A class for receiving events when audio devices are inserted or removed. | |||
| You can register a AudioIODeviceType::Listener with an~AudioIODeviceType object | |||
| using the AudioIODeviceType::addListener() method, and it will be called when | |||
| devices of that type are added or removed. | |||
| @see AudioIODeviceType::addListener, AudioIODeviceType::removeListener | |||
| */ | |||
| class Listener | |||
| { | |||
| AudioDeviceManager* manager; | |||
| int minNumInputChannels, maxNumInputChannels; | |||
| int minNumOutputChannels, maxNumOutputChannels; | |||
| bool useStereoPairs; | |||
| public: | |||
| virtual ~Listener() {} | |||
| /** Called when the list of available audio devices changes. */ | |||
| virtual void audioDeviceListChanged() = 0; | |||
| }; | |||
| /** Adds a listener that will be called when this type of device is added or | |||
| removed from the system. | |||
| */ | |||
| void addListener (Listener* listener); | |||
| /** Removes a listener that was previously added with addListener(). */ | |||
| void removeListener (Listener* listener); | |||
| /** Destructor. */ | |||
| virtual ~AudioIODeviceType(); | |||
| @@ -39918,8 +39936,12 @@ public: | |||
| protected: | |||
| explicit AudioIODeviceType (const String& typeName); | |||
| /** Synchronously calls all the registered device list change listeners. */ | |||
| void callDeviceChangeListeners(); | |||
| private: | |||
| String typeName; | |||
| ListenerList<Listener> listeners; | |||
| JUCE_DECLARE_NON_COPYABLE (AudioIODeviceType); | |||
| }; | |||
| @@ -41733,13 +41755,15 @@ private: | |||
| double cpuUsageMs, timeToCpuScale; | |||
| class CallbackHandler : public AudioIODeviceCallback, | |||
| public MidiInputCallback | |||
| public MidiInputCallback, | |||
| public AudioIODeviceType::Listener | |||
| { | |||
| public: | |||
| void audioDeviceIOCallback (const float**, int, float**, int, int); | |||
| void audioDeviceAboutToStart (AudioIODevice*); | |||
| void audioDeviceStopped(); | |||
| void handleIncomingMidiMessage (MidiInput*, const MidiMessage&); | |||
| void audioDeviceListChanged(); | |||
| AudioDeviceManager* owner; | |||
| }; | |||
| @@ -41752,6 +41776,7 @@ private: | |||
| void audioDeviceAboutToStartInt (AudioIODevice*); | |||
| void audioDeviceStoppedInt(); | |||
| void handleIncomingMidiMessageInt (MidiInput*, const MidiMessage&); | |||
| void audioDeviceListChanged(); | |||
| String restartDevice (int blockSizeToUse, double sampleRateToUse, | |||
| const BigInteger& ins, const BigInteger& outs); | |||
| @@ -41764,7 +41789,7 @@ private: | |||
| void deleteCurrentDevice(); | |||
| double chooseBestSampleRate (double preferred) const; | |||
| int chooseBestBufferSize (int preferred) const; | |||
| void insertDefaultDeviceNames (AudioDeviceSetup& setup) const; | |||
| void insertDefaultDeviceNames (AudioDeviceSetup&) const; | |||
| AudioIODeviceType* findType (const String& inputName, const String& outputName); | |||
| @@ -52451,6 +52476,7 @@ private: | |||
| void remove (const Range<int>& range, UndoManager* um, int caretPositionToMoveTo); | |||
| void getCharPosition (int index, float& x, float& y, float& lineHeight) const; | |||
| void updateCaretPosition(); | |||
| void updateValueFromText(); | |||
| void textWasChangedByValue(); | |||
| int indexAtPosition (float x, float y); | |||
| int findWordBreakAfter (int position) const; | |||
| @@ -85,6 +85,9 @@ void AudioDeviceManager::createDeviceTypesIfNeeded() | |||
| if (availableDeviceTypes.size() > 0) | |||
| currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | |||
| for (int i = 0; i < availableDeviceTypes.size(); ++i) | |||
| availableDeviceTypes.getUnchecked(i)->addListener (&callbackHandler); | |||
| } | |||
| } | |||
| @@ -94,6 +97,11 @@ const OwnedArray <AudioIODeviceType>& AudioDeviceManager::getAvailableDeviceType | |||
| return availableDeviceTypes; | |||
| } | |||
| void AudioDeviceManager::audioDeviceListChanged() | |||
| { | |||
| sendChangeMessage(); | |||
| } | |||
| //============================================================================== | |||
| static void addIfNotNull (OwnedArray <AudioIODeviceType>& list, AudioIODeviceType* const device) | |||
| { | |||
| @@ -869,6 +877,11 @@ void AudioDeviceManager::CallbackHandler::handleIncomingMidiMessage (MidiInput* | |||
| owner->handleIncomingMidiMessageInt (source, message); | |||
| } | |||
| void AudioDeviceManager::CallbackHandler::audioDeviceListChanged() | |||
| { | |||
| owner->audioDeviceListChanged(); | |||
| } | |||
| //============================================================================== | |||
| void AudioDeviceManager::playTestSound() | |||
| { | |||
| @@ -476,13 +476,15 @@ private: | |||
| //============================================================================== | |||
| class CallbackHandler : public AudioIODeviceCallback, | |||
| public MidiInputCallback | |||
| public MidiInputCallback, | |||
| public AudioIODeviceType::Listener | |||
| { | |||
| public: | |||
| void audioDeviceIOCallback (const float**, int, float**, int, int); | |||
| void audioDeviceAboutToStart (AudioIODevice*); | |||
| void audioDeviceStopped(); | |||
| void handleIncomingMidiMessage (MidiInput*, const MidiMessage&); | |||
| void audioDeviceListChanged(); | |||
| AudioDeviceManager* owner; | |||
| }; | |||
| @@ -495,6 +497,7 @@ private: | |||
| void audioDeviceAboutToStartInt (AudioIODevice*); | |||
| void audioDeviceStoppedInt(); | |||
| void handleIncomingMidiMessageInt (MidiInput*, const MidiMessage&); | |||
| void audioDeviceListChanged(); | |||
| String restartDevice (int blockSizeToUse, double sampleRateToUse, | |||
| const BigInteger& ins, const BigInteger& outs); | |||
| @@ -507,7 +510,7 @@ private: | |||
| void deleteCurrentDevice(); | |||
| double chooseBestSampleRate (double preferred) const; | |||
| int chooseBestBufferSize (int preferred) const; | |||
| void insertDefaultDeviceNames (AudioDeviceSetup& setup) const; | |||
| void insertDefaultDeviceNames (AudioDeviceSetup&) const; | |||
| AudioIODeviceType* findType (const String& inputName, const String& outputName); | |||
| @@ -40,6 +40,16 @@ AudioIODeviceType::~AudioIODeviceType() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void AudioIODeviceType::addListener (Listener* l) { listeners.add (l); } | |||
| void AudioIODeviceType::removeListener (Listener* l) { listeners.remove (l); } | |||
| void AudioIODeviceType::callDeviceChangeListeners() | |||
| { | |||
| listeners.call (&AudioIODeviceType::Listener::audioDeviceListChanged); | |||
| } | |||
| //============================================================================== | |||
| #if ! JUCE_MAC | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; } | |||
| #endif | |||
| @@ -27,6 +27,7 @@ | |||
| #define __JUCE_AUDIOIODEVICETYPE_JUCEHEADER__ | |||
| #include "juce_AudioIODevice.h" | |||
| #include "../../events/juce_ListenerList.h" | |||
| class AudioDeviceManager; | |||
| @@ -119,14 +120,32 @@ public: | |||
| const String& inputDeviceName) = 0; | |||
| //============================================================================== | |||
| struct DeviceSetupDetails | |||
| /** | |||
| A class for receiving events when audio devices are inserted or removed. | |||
| You can register a AudioIODeviceType::Listener with an~AudioIODeviceType object | |||
| using the AudioIODeviceType::addListener() method, and it will be called when | |||
| devices of that type are added or removed. | |||
| @see AudioIODeviceType::addListener, AudioIODeviceType::removeListener | |||
| */ | |||
| class Listener | |||
| { | |||
| AudioDeviceManager* manager; | |||
| int minNumInputChannels, maxNumInputChannels; | |||
| int minNumOutputChannels, maxNumOutputChannels; | |||
| bool useStereoPairs; | |||
| public: | |||
| virtual ~Listener() {} | |||
| /** Called when the list of available audio devices changes. */ | |||
| virtual void audioDeviceListChanged() = 0; | |||
| }; | |||
| /** Adds a listener that will be called when this type of device is added or | |||
| removed from the system. | |||
| */ | |||
| void addListener (Listener* listener); | |||
| /** Removes a listener that was previously added with addListener(). */ | |||
| void removeListener (Listener* listener); | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| virtual ~AudioIODeviceType(); | |||
| @@ -152,8 +171,12 @@ public: | |||
| protected: | |||
| explicit AudioIODeviceType (const String& typeName); | |||
| /** Synchronously calls all the registered device list change listeners. */ | |||
| void callDeviceChangeListeners(); | |||
| private: | |||
| String typeName; | |||
| ListenerList<Listener> listeners; | |||
| JUCE_DECLARE_NON_COPYABLE (AudioIODeviceType); | |||
| }; | |||
| @@ -33,7 +33,7 @@ | |||
| */ | |||
| #define JUCE_MAJOR_VERSION 1 | |||
| #define JUCE_MINOR_VERSION 53 | |||
| #define JUCE_BUILDNUMBER 104 | |||
| #define JUCE_BUILDNUMBER 105 | |||
| /** Current Juce version number. | |||
| @@ -1270,14 +1270,18 @@ void TextEditor::setText (const String& newText, | |||
| } | |||
| //============================================================================== | |||
| Value& TextEditor::getTextValue() | |||
| void TextEditor::updateValueFromText() | |||
| { | |||
| if (valueTextNeedsUpdating) | |||
| { | |||
| valueTextNeedsUpdating = false; | |||
| textValue = getText(); | |||
| } | |||
| } | |||
| Value& TextEditor::getTextValue() | |||
| { | |||
| updateValueFromText(); | |||
| return textValue; | |||
| } | |||
| @@ -2222,6 +2226,7 @@ void TextEditor::handleCommandMessage (const int commandId) | |||
| break; | |||
| case TextEditorDefs::focusLossMessageId: | |||
| updateValueFromText(); | |||
| listeners.callChecked (checker, &TextEditorListener::textEditorFocusLost, (TextEditor&) *this); | |||
| break; | |||
| @@ -685,6 +685,7 @@ private: | |||
| void remove (const Range<int>& range, UndoManager* um, int caretPositionToMoveTo); | |||
| void getCharPosition (int index, float& x, float& y, float& lineHeight) const; | |||
| void updateCaretPosition(); | |||
| void updateValueFromText(); | |||
| void textWasChangedByValue(); | |||
| int indexAtPosition (float x, float y); | |||
| int findWordBreakAfter (int position) const; | |||
| @@ -197,6 +197,16 @@ private: | |||
| }; | |||
| //============================================================================== | |||
| struct AudioDeviceSetupDetails | |||
| { | |||
| AudioDeviceManager* manager; | |||
| int minNumInputChannels, maxNumInputChannels; | |||
| int minNumOutputChannels, maxNumOutputChannels; | |||
| bool useStereoPairs; | |||
| }; | |||
| //============================================================================== | |||
| class AudioDeviceSettingsPanel : public Component, | |||
| public ChangeListener, | |||
| @@ -205,7 +215,7 @@ class AudioDeviceSettingsPanel : public Component, | |||
| { | |||
| public: | |||
| AudioDeviceSettingsPanel (AudioIODeviceType* type_, | |||
| AudioIODeviceType::DeviceSetupDetails& setup_, | |||
| AudioDeviceSetupDetails& setup_, | |||
| const bool hideAdvancedOptionsWithButton) | |||
| : type (type_), | |||
| setup (setup_) | |||
| @@ -475,7 +485,7 @@ public: | |||
| private: | |||
| AudioIODeviceType* const type; | |||
| const AudioIODeviceType::DeviceSetupDetails setup; | |||
| const AudioDeviceSetupDetails setup; | |||
| ScopedPointer<ComboBox> outputDeviceDropDown, inputDeviceDropDown, sampleRateDropDown, bufferSizeDropDown; | |||
| ScopedPointer<Label> outputDeviceLabel, inputDeviceLabel, sampleRateLabel, bufferSizeLabel, inputChanLabel, outputChanLabel; | |||
| @@ -661,7 +671,7 @@ public: | |||
| }; | |||
| //============================================================================== | |||
| ChannelSelectorListBox (const AudioIODeviceType::DeviceSetupDetails& setup_, | |||
| ChannelSelectorListBox (const AudioDeviceSetupDetails& setup_, | |||
| const BoxType type_, | |||
| const String& noItemsMessage_) | |||
| : ListBox (String::empty, nullptr), | |||
| @@ -804,7 +814,7 @@ public: | |||
| private: | |||
| //============================================================================== | |||
| const AudioIODeviceType::DeviceSetupDetails setup; | |||
| const AudioDeviceSetupDetails setup; | |||
| const BoxType type; | |||
| const String noItemsMessage; | |||
| StringArray items; | |||
| @@ -1076,7 +1086,7 @@ void AudioDeviceSelectorComponent::updateAllControls() | |||
| if (type != nullptr) | |||
| { | |||
| AudioIODeviceType::DeviceSetupDetails details; | |||
| AudioDeviceSetupDetails details; | |||
| details.manager = &deviceManager; | |||
| details.minNumInputChannels = minInputChannels; | |||
| details.maxNumInputChannels = maxInputChannels; | |||
| @@ -1052,6 +1052,22 @@ public: | |||
| : AudioIODeviceType ("CoreAudio"), | |||
| hasScanned (false) | |||
| { | |||
| AudioObjectPropertyAddress pa; | |||
| pa.mSelector = kAudioHardwarePropertyDevices; | |||
| pa.mScope = kAudioObjectPropertyScopeWildcard; | |||
| pa.mElement = kAudioObjectPropertyElementWildcard; | |||
| AudioObjectAddPropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, this); | |||
| } | |||
| ~CoreAudioIODeviceType() | |||
| { | |||
| AudioObjectPropertyAddress pa; | |||
| pa.mSelector = kAudioHardwarePropertyDevices; | |||
| pa.mScope = kAudioObjectPropertyScopeWildcard; | |||
| pa.mElement = kAudioObjectPropertyElementWildcard; | |||
| AudioObjectRemovePropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, this); | |||
| } | |||
| //============================================================================== | |||
| @@ -1226,6 +1242,18 @@ private: | |||
| return total; | |||
| } | |||
| void audioDeviceListChanged() | |||
| { | |||
| scanForDevices(); | |||
| callDeviceChangeListeners(); | |||
| } | |||
| static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) | |||
| { | |||
| static_cast <CoreAudioIODeviceType*> (clientData)->audioDeviceListChanged(); | |||
| return noErr; | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType); | |||
| }; | |||
| @@ -210,6 +210,7 @@ public: | |||
| virtual void drawRect (NSRect r); | |||
| virtual bool canBecomeKeyWindow(); | |||
| virtual void becomeKeyWindow(); | |||
| virtual bool windowShouldClose(); | |||
| virtual void redirectMovedOrResized(); | |||
| @@ -681,7 +682,7 @@ END_JUCE_NAMESPACE | |||
| [super becomeKeyWindow]; | |||
| if (owner != nullptr) | |||
| owner->grabFocus(); | |||
| owner->becomeKeyWindow(); | |||
| } | |||
| - (BOOL) windowShouldClose: (id) window | |||
| @@ -1601,6 +1602,12 @@ bool NSViewComponentPeer::canBecomeKeyWindow() | |||
| return (getStyleFlags() & JUCE_NAMESPACE::ComponentPeer::windowIgnoresKeyPresses) == 0; | |||
| } | |||
| void NSViewComponentPeer::becomeKeyWindow() | |||
| { | |||
| handleBroughtToFront(); | |||
| grabFocus(); | |||
| } | |||
| bool NSViewComponentPeer::windowShouldClose() | |||
| { | |||
| if (! isValidPeer (this)) | |||
| @@ -427,10 +427,10 @@ public: | |||
| view.frame = CGRectMake ((CGFloat) bounds.getX(), (CGFloat) bounds.getY(), | |||
| (CGFloat) bounds.getWidth(), (CGFloat) bounds.getHeight()); | |||
| if (lastWidth != w || lastHeight != h) | |||
| if (lastWidth != bounds.getWidth() || lastHeight != bounds.getHeight()) | |||
| { | |||
| lastWidth = w; | |||
| lastHeight = h; | |||
| lastWidth = bounds.getWidth(); | |||
| lastHeight = bounds.getHeight(); | |||
| freeGLBuffers(); | |||
| createGLBuffers(); | |||
| } | |||
| @@ -28,14 +28,55 @@ | |||
| #if JUCE_INCLUDED_FILE | |||
| //============================================================================== | |||
| class HiddenMessageWindow | |||
| { | |||
| public: | |||
| HiddenMessageWindow (const TCHAR* const messageWindowName, WNDPROC wndProc) | |||
| { | |||
| String className ("JUCE_"); | |||
| className << String::toHexString (Time::getHighResolutionTicks()); | |||
| HMODULE moduleHandle = (HMODULE) PlatformUtilities::getCurrentModuleInstanceHandle(); | |||
| WNDCLASSEX wc = { 0 }; | |||
| wc.cbSize = sizeof (wc); | |||
| wc.lpfnWndProc = wndProc; | |||
| wc.cbWndExtra = 4; | |||
| wc.hInstance = moduleHandle; | |||
| wc.lpszClassName = className.toWideCharPointer(); | |||
| atom = RegisterClassEx (&wc); | |||
| jassert (atom != 0); | |||
| hwnd = CreateWindow (getClassNameFromAtom(), messageWindowName, | |||
| 0, 0, 0, 0, 0, 0, 0, moduleHandle, 0); | |||
| jassert (hwnd != 0); | |||
| } | |||
| ~HiddenMessageWindow() | |||
| { | |||
| DestroyWindow (hwnd); | |||
| UnregisterClass (getClassNameFromAtom(), 0); | |||
| } | |||
| inline HWND getHWND() const noexcept { return hwnd; } | |||
| private: | |||
| ATOM atom; | |||
| HWND hwnd; | |||
| LPCTSTR getClassNameFromAtom() noexcept { return (LPCTSTR) MAKELONG (atom, 0); } | |||
| }; | |||
| //============================================================================== | |||
| static const unsigned int specialId = WM_APP + 0x4400; | |||
| static const unsigned int broadcastId = WM_APP + 0x4403; | |||
| static const unsigned int specialCallbackId = WM_APP + 0x4402; | |||
| static const TCHAR* const messageWindowName = _T("JUCEWindow"); | |||
| static ATOM messageWindowClassAtom = 0; | |||
| static LPCTSTR getMessageWindowClassName() noexcept { return (LPCTSTR) MAKELONG (messageWindowClassAtom, 0); } | |||
| static const TCHAR messageWindowName[] = _T("JUCEWindow"); | |||
| static ScopedPointer<HiddenMessageWindow> messageWindow; | |||
| HWND juce_messageWindowHandle = 0; | |||
| @@ -259,34 +300,14 @@ void MessageManager::doPlatformSpecificInitialisation() | |||
| { | |||
| OleInitialize (0); | |||
| // this name has to be different for each app/dll instance because otherwise | |||
| // poor old Win32 can get a bit confused (even despite it not being a process-global | |||
| // window class). | |||
| String className ("JUCEcs_"); | |||
| className << (int) (Time::getHighResolutionTicks() & 0x7fffffff); | |||
| HMODULE moduleHandle = (HMODULE) PlatformUtilities::getCurrentModuleInstanceHandle(); | |||
| WNDCLASSEX wc = { 0 }; | |||
| wc.cbSize = sizeof (wc); | |||
| wc.lpfnWndProc = (WNDPROC) juce_MessageWndProc; | |||
| wc.cbWndExtra = 4; | |||
| wc.hInstance = moduleHandle; | |||
| wc.lpszClassName = className.toWideCharPointer(); | |||
| messageWindowClassAtom = RegisterClassEx (&wc); | |||
| jassert (messageWindowClassAtom != 0); | |||
| juce_messageWindowHandle = CreateWindow (getMessageWindowClassName(), messageWindowName, | |||
| 0, 0, 0, 0, 0, 0, 0, moduleHandle, 0); | |||
| jassert (juce_messageWindowHandle != 0); | |||
| messageWindow = new HiddenMessageWindow (messageWindowName, (WNDPROC) juce_MessageWndProc); | |||
| juce_messageWindowHandle = messageWindow->getHWND(); | |||
| } | |||
| void MessageManager::doPlatformSpecificShutdown() | |||
| { | |||
| DestroyWindow (juce_messageWindowHandle); | |||
| UnregisterClass (getMessageWindowClassName(), 0); | |||
| messageWindow = nullptr; | |||
| OleUninitialize(); | |||
| } | |||
| @@ -632,8 +632,6 @@ public: | |||
| bool initialise() | |||
| { | |||
| double defaultSampleRateIn = 0, defaultSampleRateOut = 0; | |||
| int minBufferSizeIn = 0, defaultBufferSizeIn = 0, minBufferSizeOut = 0, defaultBufferSizeOut = 0; | |||
| latencyIn = latencyOut = 0; | |||
| Array <double> ratesIn, ratesOut; | |||
| @@ -965,12 +963,10 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType | |||
| public: | |||
| WASAPIAudioIODeviceType() | |||
| : AudioIODeviceType ("Windows Audio"), | |||
| deviceChangeCatcher (_T("Windows Audio"), (WNDPROC) deviceChangeEventCallback), | |||
| hasScanned (false) | |||
| { | |||
| } | |||
| ~WASAPIAudioIODeviceType() | |||
| { | |||
| SetWindowLongPtr (deviceChangeCatcher.getHWND(), GWLP_USERDATA, (LONG_PTR) this); | |||
| } | |||
| //============================================================================== | |||
| @@ -983,68 +979,8 @@ public: | |||
| outputDeviceIds.clear(); | |||
| inputDeviceIds.clear(); | |||
| ComSmartPtr <IMMDeviceEnumerator> enumerator; | |||
| if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) | |||
| return; | |||
| const String defaultRenderer = getDefaultEndpoint (enumerator, false); | |||
| const String defaultCapture = getDefaultEndpoint (enumerator, true); | |||
| ComSmartPtr <IMMDeviceCollection> deviceCollection; | |||
| UINT32 numDevices = 0; | |||
| if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())) | |||
| && check (deviceCollection->GetCount (&numDevices)))) | |||
| return; | |||
| for (UINT32 i = 0; i < numDevices; ++i) | |||
| { | |||
| ComSmartPtr <IMMDevice> device; | |||
| if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress()))) | |||
| continue; | |||
| const String deviceId (getDeviceID (device)); | |||
| DWORD state = 0; | |||
| if (! check (device->GetState (&state))) | |||
| continue; | |||
| if (state != DEVICE_STATE_ACTIVE) | |||
| continue; | |||
| String name; | |||
| { | |||
| ComSmartPtr <IPropertyStore> properties; | |||
| if (! check (device->OpenPropertyStore (STGM_READ, properties.resetAndGetPointerAddress()))) | |||
| continue; | |||
| PROPVARIANT value; | |||
| PropVariantInit (&value); | |||
| if (check (properties->GetValue (PKEY_Device_FriendlyName, &value))) | |||
| name = value.pwszVal; | |||
| PropVariantClear (&value); | |||
| } | |||
| const EDataFlow flow = getDataFlow (device); | |||
| if (flow == eRender) | |||
| { | |||
| const int index = (deviceId == defaultRenderer) ? 0 : -1; | |||
| outputDeviceIds.insert (index, deviceId); | |||
| outputDeviceNames.insert (index, name); | |||
| } | |||
| else if (flow == eCapture) | |||
| { | |||
| const int index = (deviceId == defaultCapture) ? 0 : -1; | |||
| inputDeviceIds.insert (index, deviceId); | |||
| inputDeviceNames.insert (index, name); | |||
| } | |||
| } | |||
| inputDeviceNames.appendNumbersToDuplicates (false, false); | |||
| outputDeviceNames.appendNumbersToDuplicates (false, false); | |||
| scan (outputDeviceNames, inputDeviceNames, | |||
| outputDeviceIds, inputDeviceIds); | |||
| } | |||
| StringArray getDeviceNames (bool wantInputNames) const | |||
| @@ -1102,6 +1038,7 @@ public: | |||
| StringArray inputDeviceNames, inputDeviceIds; | |||
| private: | |||
| HiddenMessageWindow deviceChangeCatcher; | |||
| bool hasScanned; | |||
| //============================================================================== | |||
| @@ -1115,7 +1052,7 @@ private: | |||
| WCHAR* deviceId = nullptr; | |||
| if (check (dev->GetId (&deviceId))) | |||
| { | |||
| s = String (deviceId); | |||
| s = deviceId; | |||
| CoTaskMemFree (deviceId); | |||
| } | |||
| @@ -1125,6 +1062,106 @@ private: | |||
| return s; | |||
| } | |||
| //============================================================================== | |||
| void scan (StringArray& outputDeviceNames, | |||
| StringArray& inputDeviceNames, | |||
| StringArray& outputDeviceIds, | |||
| StringArray& inputDeviceIds) | |||
| { | |||
| ComSmartPtr <IMMDeviceEnumerator> enumerator; | |||
| if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) | |||
| return; | |||
| const String defaultRenderer (getDefaultEndpoint (enumerator, false)); | |||
| const String defaultCapture (getDefaultEndpoint (enumerator, true)); | |||
| ComSmartPtr <IMMDeviceCollection> deviceCollection; | |||
| UINT32 numDevices = 0; | |||
| if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())) | |||
| && check (deviceCollection->GetCount (&numDevices)))) | |||
| return; | |||
| for (UINT32 i = 0; i < numDevices; ++i) | |||
| { | |||
| ComSmartPtr <IMMDevice> device; | |||
| if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress()))) | |||
| continue; | |||
| DWORD state = 0; | |||
| if (! (check (device->GetState (&state)) && state == DEVICE_STATE_ACTIVE)) | |||
| continue; | |||
| const String deviceId (getDeviceID (device)); | |||
| String name; | |||
| { | |||
| ComSmartPtr <IPropertyStore> properties; | |||
| if (! check (device->OpenPropertyStore (STGM_READ, properties.resetAndGetPointerAddress()))) | |||
| continue; | |||
| PROPVARIANT value; | |||
| PropVariantInit (&value); | |||
| if (check (properties->GetValue (PKEY_Device_FriendlyName, &value))) | |||
| name = value.pwszVal; | |||
| PropVariantClear (&value); | |||
| } | |||
| const EDataFlow flow = getDataFlow (device); | |||
| if (flow == eRender) | |||
| { | |||
| const int index = (deviceId == defaultRenderer) ? 0 : -1; | |||
| outputDeviceIds.insert (index, deviceId); | |||
| outputDeviceNames.insert (index, name); | |||
| } | |||
| else if (flow == eCapture) | |||
| { | |||
| const int index = (deviceId == defaultCapture) ? 0 : -1; | |||
| inputDeviceIds.insert (index, deviceId); | |||
| inputDeviceNames.insert (index, name); | |||
| } | |||
| } | |||
| inputDeviceNames.appendNumbersToDuplicates (false, false); | |||
| outputDeviceNames.appendNumbersToDuplicates (false, false); | |||
| } | |||
| //============================================================================== | |||
| static LRESULT CALLBACK deviceChangeEventCallback (HWND h, const UINT message, | |||
| const WPARAM wParam, const LPARAM lParam) | |||
| { | |||
| if (message == WM_DEVICECHANGE | |||
| && (wParam == 0x8000 /*DBT_DEVICEARRIVAL*/ | |||
| || wParam == 0x8004 /*DBT_DEVICEREMOVECOMPLETE*/)) | |||
| { | |||
| ((WASAPIAudioIODeviceType*) GetWindowLongPtr (h, GWLP_USERDATA))->handleDeviceChange(); | |||
| } | |||
| return DefWindowProc (h, message, wParam, lParam); | |||
| } | |||
| void handleDeviceChange() | |||
| { | |||
| StringArray newOutNames, newInNames, newOutIds, newInIds; | |||
| scan (newOutNames, newInNames, newOutIds, newInIds); | |||
| if (newOutNames != outputDeviceNames | |||
| || newInNames != inputDeviceNames | |||
| || newOutIds != outputDeviceIds | |||
| || newInIds != inputDeviceIds) | |||
| { | |||
| hasScanned = true; | |||
| outputDeviceNames = newOutNames; | |||
| inputDeviceNames = newInNames; | |||
| outputDeviceIds = newOutIds; | |||
| inputDeviceIds = newInIds; | |||
| callDeviceChangeListeners(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType); | |||
| }; | |||