diff --git a/modules/juce_audio_devices/juce_audio_devices.cpp b/modules/juce_audio_devices/juce_audio_devices.cpp index f9c99bd800..a4b079c0ce 100644 --- a/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/modules/juce_audio_devices/juce_audio_devices.cpp @@ -43,6 +43,14 @@ #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 #define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 +#ifndef JUCE_USE_WINRT_MIDI + #define JUCE_USE_WINRT_MIDI 0 +#endif + +#if JUCE_USE_WINRT_MIDI + #define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1 +#endif + #include "juce_audio_devices.h" //============================================================================== @@ -70,6 +78,30 @@ #include #endif + #if JUCE_USE_WINRT_MIDI + /* If you cannot find any of the header files below then you are probably + attempting to use the Windows 10 Bluetooth Low Energy API. For this to work you + need to install version 10.0.14393.0 of the Windows Standalone SDK and add the + path to the WinRT headers to your build system. This path should have the form + "C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt". + + Also please note that Microsoft's Bluetooth MIDI stack has multiple issues, so + this API is EXPERIMENTAL - use at your own risk! + */ + #include + #include + #include + #include + #if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4467) + #endif + #include + #if JUCE_MSVC + #pragma warning (pop) + #endif + #endif + #if JUCE_ASIO /* This is very frustrating - we only need to use a handful of definitions from a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy diff --git a/modules/juce_audio_devices/juce_audio_devices.h b/modules/juce_audio_devices/juce_audio_devices.h index fe3c9b12f3..cac19bc90b 100644 --- a/modules/juce_audio_devices/juce_audio_devices.h +++ b/modules/juce_audio_devices/juce_audio_devices.h @@ -123,6 +123,26 @@ #endif #endif +/** Config: JUCE_USE_WINRT_MIDI + *** + EXPERIMENTAL - Microsoft's Bluetooth MIDI stack has multiple issues, + use at your own risk! + *** + + Enables the use of the Windows Runtime API for MIDI, which supports + Bluetooth Low Energy connections on computers with the Anniversary Update + of Windows 10. + + To compile with this flag requires version 10.0.14393.0 of the Windows + Standalone SDK and you must add the path to the WinRT headers. This path + should be something similar to + "C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt". +*/ +#ifndef JUCE_USE_WINRT_MIDI + #define JUCE_USE_WINRT_MIDI 0 +#endif + + //============================================================================== namespace juce { diff --git a/modules/juce_audio_devices/midi_io/juce_MidiInput.h b/modules/juce_audio_devices/midi_io/juce_MidiInput.h index d347f91aa2..182c53e8de 100644 --- a/modules/juce_audio_devices/midi_io/juce_MidiInput.h +++ b/modules/juce_audio_devices/midi_io/juce_MidiInput.h @@ -172,7 +172,7 @@ public: private: //============================================================================== String name; - void* internal; + void* internal = nullptr; // The input objects are created with the openDevice() method. explicit MidiInput (const String&); diff --git a/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp b/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp index c9be2a9861..72d0266aaf 100644 --- a/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp +++ b/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp @@ -38,7 +38,7 @@ struct MidiOutput::PendingMessage PendingMessage* next; }; -MidiOutput::MidiOutput(const String& midiName) +MidiOutput::MidiOutput (const String& midiName) : Thread ("midi out"), internal (nullptr), firstMessage (nullptr), diff --git a/modules/juce_audio_devices/midi_io/juce_MidiOutput.h b/modules/juce_audio_devices/midi_io/juce_MidiOutput.h index 5cb4d58c19..08a716fa37 100644 --- a/modules/juce_audio_devices/midi_io/juce_MidiOutput.h +++ b/modules/juce_audio_devices/midi_io/juce_MidiOutput.h @@ -136,13 +136,13 @@ public: private: //============================================================================== - void* internal; + void* internal = nullptr; CriticalSection lock; struct PendingMessage; PendingMessage* firstMessage; String name; - MidiOutput(const String& midiName); // These objects are created with the openDevice() method. + MidiOutput (const String& midiName); // These objects are created with the openDevice() method. void run() override; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput) diff --git a/modules/juce_audio_devices/native/juce_win32_Midi.cpp b/modules/juce_audio_devices/native/juce_win32_Midi.cpp index 7189ff48af..c3b86da7da 100644 --- a/modules/juce_audio_devices/native/juce_win32_Midi.cpp +++ b/modules/juce_audio_devices/native/juce_win32_Midi.cpp @@ -28,472 +28,1206 @@ ============================================================================== */ -class MidiInCollector +struct MidiServiceType { -public: - MidiInCollector (MidiInput* const input_, - MidiInputCallback& callback_) - : deviceHandle (0), - input (input_), - callback (callback_), - concatenator (4096), - isStarted (false), - startTime (0) + struct InputWrapper { - } + virtual ~InputWrapper() {} - ~MidiInCollector() + virtual String getDeviceName() = 0; + + virtual void start() = 0; + virtual void stop() = 0; + }; + + struct OutputWrapper { - stop(); + virtual ~OutputWrapper () {} + + virtual String getDeviceName() = 0; + + virtual void sendMessageNow (const MidiMessage&) = 0; + }; + + MidiServiceType() {} + virtual ~MidiServiceType() {} + + virtual StringArray getDevices (bool) = 0; + virtual int getDefaultDeviceIndex (bool) = 0; + + virtual InputWrapper* createInputWrapper (MidiInput* const, + const int, + MidiInputCallback* const callback) = 0; + virtual OutputWrapper* createOutputWrapper (const int) = 0; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType) +}; + +class MidiService : public DeletedAtShutdown +{ +public: + MidiServiceType* getService(); + + juce_DeclareSingleton (MidiService, true) + +private: + MidiService(); + + ScopedPointer internal; +}; - if (deviceHandle != 0) +juce_ImplementSingleton (MidiService) + +//============================================================================== +class WindowsMidiService : public MidiServiceType +{ +private: + struct WindowsInputWrapper : public InputWrapper + { + class MidiInCollector { - for (int count = 5; --count >= 0;) + public: + MidiInCollector (WindowsMidiService& s, + MidiInput* const inputDevice, + MidiInputCallback& cb) + : midiService (s), + input (inputDevice), + callback (cb) { - if (midiInClose (deviceHandle) == MMSYSERR_NOERROR) - break; + } + + ~MidiInCollector() + { + stop(); + + if (deviceHandle != 0) + { + for (int count = 5; --count >= 0;) + { + if (midiInClose (deviceHandle) == MMSYSERR_NOERROR) + break; + + Sleep (20); + } + } + } + + void handleMessage (const uint8* bytes, const uint32 timeStamp) + { + if (bytes[0] >= 0x80 && isStarted) + { + concatenator.pushMidiData (bytes, + MidiMessage::getMessageLengthFromFirstByte (bytes[0]), + convertTimeStamp (timeStamp), + input, + callback); + writeFinishedBlocks(); + } + } + + void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp) + { + if (isStarted && hdr->dwBytesRecorded > 0) + { + concatenator.pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded, + convertTimeStamp (timeStamp), input, callback); + writeFinishedBlocks(); + } + } + + void start() + { + if (deviceHandle != 0 && ! isStarted) + { + midiService.activeMidiCollectors.addIfNotAlreadyThere (this); + + for (int i = 0; i < (int) numHeaders; ++i) + { + headers[i].prepare (deviceHandle); + headers[i].write (deviceHandle); + } + + startTime = Time::getMillisecondCounterHiRes(); + MMRESULT res = midiInStart (deviceHandle); + + if (res == MMSYSERR_NOERROR) + { + concatenator.reset(); + isStarted = true; + } + else + { + unprepareAllHeaders(); + } + } + } + + void stop() + { + if (isStarted) + { + isStarted = false; + midiInReset (deviceHandle); + midiInStop (deviceHandle); + midiService.activeMidiCollectors.removeFirstMatchingValue (this); + unprepareAllHeaders(); + concatenator.reset(); + } + } + + static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, + DWORD_PTR midiMessage, DWORD_PTR timeStamp) + { + MidiInCollector* const collector = reinterpret_cast (dwInstance); + + if (collector->midiService.activeMidiCollectors.contains (collector)) + { + if (uMsg == MIM_DATA) + collector->handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp); + else if (uMsg == MIM_LONGDATA) + collector->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp); + } + } + + HMIDIIN deviceHandle = 0; + + private: + WindowsMidiService& midiService; + MidiInput* input; + MidiInputCallback& callback; + MidiDataConcatenator concatenator { 4096 }; + bool volatile isStarted = false; + double startTime = 0; + + class MidiHeader + { + public: + MidiHeader() {} + + void prepare (HMIDIIN device) + { + zerostruct (hdr); + hdr.lpData = data; + hdr.dwBufferLength = (DWORD) numElementsInArray (data); + + midiInPrepareHeader (device, &hdr, sizeof (hdr)); + } + + void unprepare (HMIDIIN device) + { + if ((hdr.dwFlags & WHDR_DONE) != 0) + { + int c = 10; + while (--c >= 0 && midiInUnprepareHeader (device, &hdr, sizeof (hdr)) == MIDIERR_STILLPLAYING) + Thread::sleep (20); + + jassert (c >= 0); + } + } + + void write (HMIDIIN device) + { + hdr.dwBytesRecorded = 0; + midiInAddBuffer (device, &hdr, sizeof (hdr)); + } + + void writeIfFinished (HMIDIIN device) + { + if ((hdr.dwFlags & WHDR_DONE) != 0) + write (device); + } + + private: + MIDIHDR hdr; + char data [256]; + + JUCE_DECLARE_NON_COPYABLE (MidiHeader) + }; + + enum { numHeaders = 32 }; + MidiHeader headers [numHeaders]; + + void writeFinishedBlocks() + { + for (int i = 0; i < (int) numHeaders; ++i) + headers[i].writeIfFinished (deviceHandle); + } + + void unprepareAllHeaders() + { + for (int i = 0; i < (int) numHeaders; ++i) + headers[i].unprepare (deviceHandle); + } + + double convertTimeStamp (uint32 timeStamp) + { + double t = startTime + timeStamp; + + const double now = Time::getMillisecondCounterHiRes(); + if (t > now) + { + if (t > now + 2.0) + startTime -= 1.0; + + t = now; + } + + return t * 0.001; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector) + }; + + WindowsInputWrapper (WindowsMidiService& parentService, + MidiInput* const input, + const int index, + MidiInputCallback* const callback) + { + UINT deviceId = MIDI_MAPPER; + int n = 0; + + const UINT num = midiInGetNumDevs(); + + for (UINT i = 0; i < num; ++i) + { + MIDIINCAPS mc = { 0 }; + + if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) + { + if (index == n) + { + deviceId = i; + deviceName = String (mc.szPname, (size_t) numElementsInArray (mc.szPname)); + break; + } + + ++n; + } + } + + collector = new MidiInCollector (parentService, input, *callback); - Sleep (20); + HMIDIIN h; + MMRESULT err = midiInOpen (&h, deviceId, + (DWORD_PTR) &MidiInCollector::midiInCallback, + (DWORD_PTR) (MidiInCollector*) collector.get(), + CALLBACK_FUNCTION); + + if (err != MMSYSERR_NOERROR) + throw std::runtime_error ("Failed to create Windows input device wrapper"); + + collector->deviceHandle = h; + } + + ~WindowsInputWrapper() {} + + static StringArray getDevices() + { + StringArray s; + const UINT num = midiInGetNumDevs(); + + for (UINT i = 0; i < num; ++i) + { + MIDIINCAPS mc = { 0 }; + + if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) + s.add (String (mc.szPname, sizeof (mc.szPname))); } + + return s; } - } - //============================================================================== - void handleMessage (const uint8* bytes, const uint32 timeStamp) - { - if (bytes[0] >= 0x80 && isStarted) + static int getDefaultDeviceIndex() { - concatenator.pushMidiData (bytes, MidiMessage::getMessageLengthFromFirstByte (bytes[0]), - convertTimeStamp (timeStamp), input, callback); - writeFinishedBlocks(); + return 0; } - } - void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp) - { - if (isStarted && hdr->dwBytesRecorded > 0) + void start() override { collector->start(); } + void stop() override { collector->stop(); } + + String getDeviceName() override { - concatenator.pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded, - convertTimeStamp (timeStamp), input, callback); - writeFinishedBlocks(); + return deviceName; } - } - void start() + String deviceName; + ScopedPointer collector; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsInputWrapper) + }; + + struct WindowsOutputWrapper : public OutputWrapper { - if (deviceHandle != 0 && ! isStarted) + struct MidiOutHandle + { + int refCount; + UINT deviceId; + HMIDIOUT handle; + + private: + JUCE_LEAK_DETECTOR (MidiOutHandle) + }; + + WindowsOutputWrapper (WindowsMidiService& p, const int index) + : parent (p) { - activeMidiCollectors.addIfNotAlreadyThere (this); + UINT deviceId = MIDI_MAPPER; + const UINT num = midiOutGetNumDevs(); + int n = 0; - for (int i = 0; i < (int) numHeaders; ++i) + for (UINT i = 0; i < num; ++i) { - headers[i].prepare (deviceHandle); - headers[i].write (deviceHandle); - } + MIDIOUTCAPS mc = { 0 }; - startTime = Time::getMillisecondCounterHiRes(); - MMRESULT res = midiInStart (deviceHandle); + if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) + { + String name = String (mc.szPname, sizeof (mc.szPname)); + + // use the microsoft sw synth as a default - best not to allow deviceId + // to be MIDI_MAPPER, or else device sharing breaks + if (name.containsIgnoreCase ("microsoft")) + deviceId = i; + + if (index == n) + { + deviceName = name; + deviceId = i; + break; + } - if (res == MMSYSERR_NOERROR) + ++n; + } + } + + for (int i = parent.activeOutputHandles.size(); --i >= 0;) { - concatenator.reset(); - isStarted = true; + han = parent.activeOutputHandles.getUnchecked (i); + + if (han->deviceId == deviceId) + { + han->refCount++; + + return; + } } - else + + for (int i = 4; --i >= 0;) { - unprepareAllHeaders(); + HMIDIOUT h = 0; + MMRESULT res = midiOutOpen (&h, deviceId, 0, 0, CALLBACK_NULL); + + if (res == MMSYSERR_NOERROR) + { + han = new MidiOutHandle(); + han->deviceId = deviceId; + han->refCount = 1; + han->handle = h; + parent.activeOutputHandles.add (han); + + return; + } + else if (res == MMSYSERR_ALLOCATED) + { + Sleep (100); + } + else + { + break; + } } + + throw std::runtime_error ("Failed to create Windows output device wrapper"); } - } - void stop() - { - if (isStarted) + ~WindowsOutputWrapper() { - isStarted = false; - midiInReset (deviceHandle); - midiInStop (deviceHandle); - activeMidiCollectors.removeFirstMatchingValue (this); - unprepareAllHeaders(); - concatenator.reset(); + if (parent.activeOutputHandles.contains (han.get()) && --(han->refCount) == 0) + { + midiOutClose (han->handle); + parent.activeOutputHandles.removeFirstMatchingValue (han.get()); + } } - } - static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, - DWORD_PTR midiMessage, DWORD_PTR timeStamp) - { - MidiInCollector* const collector = reinterpret_cast (dwInstance); - - if (activeMidiCollectors.contains (collector)) + void sendMessageNow (const MidiMessage& message) override { - if (uMsg == MIM_DATA) - collector->handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp); - else if (uMsg == MIM_LONGDATA) - collector->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp); - } - } + if (message.getRawDataSize() > 3 || message.isSysEx()) + { + MIDIHDR h = { 0 }; - HMIDIIN deviceHandle; + h.lpData = (char*) message.getRawData(); + h.dwBytesRecorded = h.dwBufferLength = (DWORD) message.getRawDataSize(); -private: - static Array activeMidiCollectors; + if (midiOutPrepareHeader (han->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR) + { + MMRESULT res = midiOutLongMsg (han->handle, &h, sizeof (MIDIHDR)); - MidiInput* input; - MidiInputCallback& callback; - MidiDataConcatenator concatenator; - bool volatile isStarted; - double startTime; + if (res == MMSYSERR_NOERROR) + { + while ((h.dwFlags & MHDR_DONE) == 0) + Sleep (1); - class MidiHeader - { - public: - MidiHeader() {} + int count = 500; // 1 sec timeout - void prepare (HMIDIIN device) - { - zerostruct (hdr); - hdr.lpData = data; - hdr.dwBufferLength = (DWORD) numElementsInArray (data); + while (--count >= 0) + { + res = midiOutUnprepareHeader (han->handle, &h, sizeof (MIDIHDR)); - midiInPrepareHeader (device, &hdr, sizeof (hdr)); + if (res == MIDIERR_STILLPLAYING) + Sleep (2); + else + break; + } + } + } + } + else + { + for (int i = 0; i < 50; ++i) + { + if (midiOutShortMsg (han->handle, *(unsigned int*) message.getRawData()) != MIDIERR_NOTREADY) + break; + + Sleep (1); + } + } } - void unprepare (HMIDIIN device) + static StringArray getDevices() { - if ((hdr.dwFlags & WHDR_DONE) != 0) + StringArray s; + const UINT num = midiOutGetNumDevs(); + + for (UINT i = 0; i < num; ++i) { - int c = 10; - while (--c >= 0 && midiInUnprepareHeader (device, &hdr, sizeof (hdr)) == MIDIERR_STILLPLAYING) - Thread::sleep (20); + MIDIOUTCAPS mc = { 0 }; - jassert (c >= 0); + if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) + s.add (String (mc.szPname, sizeof (mc.szPname))); } + + return s; } - void write (HMIDIIN device) + static int getDefaultDeviceIndex() { - hdr.dwBytesRecorded = 0; - midiInAddBuffer (device, &hdr, sizeof (hdr)); + const UINT num = midiOutGetNumDevs(); + int n = 0; + + for (UINT i = 0; i < num; ++i) + { + MIDIOUTCAPS mc = { 0 }; + + if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) + { + if ((mc.wTechnology & MOD_MAPPER) != 0) + return n; + + ++n; + } + } + + return 0; } - void writeIfFinished (HMIDIIN device) + String getDeviceName() override { - if ((hdr.dwFlags & WHDR_DONE) != 0) - write (device); + return deviceName; } - private: - MIDIHDR hdr; - char data [256]; + WindowsMidiService& parent; + String deviceName; - JUCE_DECLARE_NON_COPYABLE (MidiHeader) + ScopedPointer han; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsOutputWrapper) }; - enum { numHeaders = 32 }; - MidiHeader headers [numHeaders]; +public: + WindowsMidiService() {} + + StringArray getDevices (bool isInput) override + { + return isInput ? WindowsInputWrapper::getDevices() + : WindowsOutputWrapper::getDevices(); + } - void writeFinishedBlocks() + int getDefaultDeviceIndex (bool isInput) override { - for (int i = 0; i < (int) numHeaders; ++i) - headers[i].writeIfFinished (deviceHandle); + return isInput ? WindowsInputWrapper::getDefaultDeviceIndex() + : WindowsOutputWrapper::getDefaultDeviceIndex(); } - void unprepareAllHeaders() + InputWrapper* createInputWrapper (MidiInput* const input, + const int index, + MidiInputCallback* const callback) override { - for (int i = 0; i < (int) numHeaders; ++i) - headers[i].unprepare (deviceHandle); + return new WindowsInputWrapper (*this, input, index, callback); } - double convertTimeStamp (uint32 timeStamp) + OutputWrapper* createOutputWrapper (const int index) override { - double t = startTime + timeStamp; + return new WindowsOutputWrapper (*this, index); + } + +private: + Array activeMidiCollectors; + Array activeOutputHandles; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMidiService) +}; + +//============================================================================== +#if JUCE_USE_WINRT_MIDI - const double now = Time::getMillisecondCounterHiRes(); - if (t > now) +using namespace Microsoft::WRL; + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Devices::Midi; +using namespace ABI::Windows::Devices::Enumeration; +using namespace ABI::Windows::Storage::Streams; + +class WinRTMidiService : public MidiServiceType +{ +private: + template + struct MidiIODeviceWatcher + { + struct DeviceInfo { - if (t > now + 2.0) - startTime -= 1.0; + String name; + String id; + bool isDefault = false; + }; - t = now; + MidiIODeviceWatcher (ComSmartPtr& comFactory) + : factory (comFactory) + { } - return t * 0.001; - } + ~MidiIODeviceWatcher() + { + stop(); + } - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector) -}; + bool start() + { + HSTRING deviceSelector; + HRESULT hr = factory->GetDeviceSelector (&deviceSelector); + if (FAILED (hr)) + return false; + + auto deviceInformationFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Devices_Enumeration_DeviceInformation[0]); + if (deviceInformationFactory == nullptr) + return false; + + hr = deviceInformationFactory->CreateWatcherAqsFilter (deviceSelector, watcher.resetAndGetPointerAddress()); + if (FAILED (hr)) + return false; + + hr = watcher->add_Added ( + Callback>( + [this] (IDeviceWatcher*, IDeviceInformation* info) { return addDevice (info); } + ).Get(), + &deviceAddedToken); + if (FAILED (hr)) + return false; + + hr = watcher->add_Removed ( + Callback>( + [this] (IDeviceWatcher*, IDeviceInformationUpdate* info) { return removeDevice (info); } + ).Get(), + &deviceRemovedToken); + if (FAILED (hr)) + return false; + + hr = watcher->Start(); + if (FAILED (hr)) + return false; + + return true; + } + + bool stop() + { + if (watcher == nullptr) + return true; -Array MidiInCollector::activeMidiCollectors; + if (deviceAddedToken.value != 0) + { + HRESULT hr = watcher->remove_Added (deviceAddedToken); + if (FAILED (hr)) + return false; + deviceAddedToken.value = 0; + } -//============================================================================== -StringArray MidiInput::getDevices() -{ - StringArray s; - const UINT num = midiInGetNumDevs(); + if (deviceRemovedToken.value != 0) + { + HRESULT hr = watcher->remove_Removed (deviceRemovedToken); + if (FAILED (hr)) + return false; - for (UINT i = 0; i < num; ++i) - { - MIDIINCAPS mc = { 0 }; + deviceRemovedToken.value = 0; + } - if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) - s.add (String (mc.szPname, sizeof (mc.szPname))); - } + HRESULT hr = watcher->Stop(); + if (FAILED (hr)) + return false; - return s; -} + watcher = nullptr; + return true; + } -int MidiInput::getDefaultDeviceIndex() -{ - return 0; -} + HRESULT addDevice (IDeviceInformation* addedDeviceInfo) + { + boolean isEnabled; + HRESULT hr = addedDeviceInfo->get_IsEnabled (&isEnabled); + if (FAILED (hr)) + return S_OK; -MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const callback) -{ - if (callback == nullptr) - return nullptr; + if (! isEnabled) + return S_OK; - UINT deviceId = MIDI_MAPPER; - int n = 0; - String name; + const ScopedLock lock (deviceChanges); - const UINT num = midiInGetNumDevs(); + DeviceInfo info; - for (UINT i = 0; i < num; ++i) - { - MIDIINCAPS mc = { 0 }; + HSTRING name; + hr = addedDeviceInfo->get_Name (&name); + if (FAILED (hr)) + return S_OK; - if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) + info.name = WinRTWrapper::getInstance()->hStringToString (name); + + HSTRING id; + hr = addedDeviceInfo->get_Id (&id); + if (FAILED (hr)) + return S_OK; + + info.id = WinRTWrapper::getInstance()->hStringToString (id); + + boolean isDefault; + hr = addedDeviceInfo->get_IsDefault (&isDefault); + if (FAILED (hr)) + return S_OK; + + info.isDefault = isDefault != 0; + + connectedDevices.add (info); + + return S_OK; + } + + HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo) { - if (index == n) + const ScopedLock lock (deviceChanges); + + HSTRING removedDeviceIdHstr; + removedDeviceInfo->get_Id (&removedDeviceIdHstr); + String removedDeviceId = WinRTWrapper::getInstance()->hStringToString (removedDeviceIdHstr); + + for (int i = 0; i < connectedDevices.size(); ++i) { - deviceId = i; - name = String (mc.szPname, (size_t) numElementsInArray (mc.szPname)); - break; + if (connectedDevices[i].id == removedDeviceId) + { + connectedDevices.remove (i); + break; + } } - ++n; + return S_OK; } - } - ScopedPointer in (new MidiInput (name)); - ScopedPointer collector (new MidiInCollector (in, *callback)); + StringArray getDevices() + { + { + const ScopedLock lock (deviceChanges); + lastQueriedConnectedDevices = connectedDevices; + } - HMIDIIN h; - MMRESULT err = midiInOpen (&h, deviceId, - (DWORD_PTR) &MidiInCollector::midiInCallback, - (DWORD_PTR) (MidiInCollector*) collector, - CALLBACK_FUNCTION); + StringArray result; + for (auto info : lastQueriedConnectedDevices.get()) + result.add (info.name); - if (err == MMSYSERR_NOERROR) - { - collector->deviceHandle = h; - in->internal = collector.release(); - return in.release(); - } + return result; + } - return nullptr; -} + int getDefaultDeviceIndex() + { + auto& lastDevices = lastQueriedConnectedDevices.get(); + for (int i = 0; i < lastDevices.size(); ++i) + { + if (lastDevices[i].isDefault) + return i; + } -MidiInput::MidiInput (const String& name_) - : name (name_), - internal (0) -{ -} + return 0; + } -MidiInput::~MidiInput() -{ - delete static_cast (internal); -} + String getDeviceNameFromIndex (const int index) + { + if (isPositiveAndBelow (index, lastQueriedConnectedDevices.get().size())) + return lastQueriedConnectedDevices.get()[index].name; -void MidiInput::start() { static_cast (internal)->start(); } -void MidiInput::stop() { static_cast (internal)->stop(); } + return {}; + } + String getDeviceID (const String name) + { + const ScopedLock lock (deviceChanges); -//============================================================================== -struct MidiOutHandle -{ - int refCount; - UINT deviceId; - HMIDIOUT handle; + for (auto info : connectedDevices) + if (info.name == name) + return info.id; - static Array activeHandles; + return {}; + } -private: - JUCE_LEAK_DETECTOR (MidiOutHandle) -}; + ComSmartPtr& factory; -Array MidiOutHandle::activeHandles; + EventRegistrationToken deviceAddedToken { 0 }, deviceRemovedToken { 0 }; -//============================================================================== -StringArray MidiOutput::getDevices() -{ - StringArray s; - const UINT num = midiOutGetNumDevs(); + ComSmartPtr watcher; + Array connectedDevices; + CriticalSection deviceChanges; + ThreadLocalValue> lastQueriedConnectedDevices; - for (UINT i = 0; i < num; ++i) - { - MIDIOUTCAPS mc = { 0 }; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher); + }; - if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) - s.add (String (mc.szPname, sizeof (mc.szPname))); - } + template + class OpenMidiPortThread : public Thread + { + public: + OpenMidiPortThread (String threadName, + String midiDeviceId, + ComSmartPtr& comFactory, + ComSmartPtr& comPort) + : Thread (threadName), + deviceId (midiDeviceId), + factory (comFactory), + port (comPort) + { + } - return s; -} + ~OpenMidiPortThread() + { + } -int MidiOutput::getDefaultDeviceIndex() -{ - const UINT num = midiOutGetNumDevs(); - int n = 0; + void run() override + { + WinRTWrapper::ScopedHString hDeviceId (deviceId); + ComSmartPtr> asyncOp; + HRESULT hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress()); + if (FAILED (hr)) + return; + + hr = asyncOp->put_Completed (Callback> ( + [this] (IAsyncOperation* asyncOpPtr, AsyncStatus) + { + if (asyncOpPtr == nullptr) + return E_ABORT; - for (UINT i = 0; i < num; ++i) - { - MIDIOUTCAPS mc = { 0 }; + HRESULT hr = asyncOpPtr->GetResults (port.resetAndGetPointerAddress()); + if (FAILED (hr)) + return hr; - if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) - { - if ((mc.wTechnology & MOD_MAPPER) != 0) - return n; + portOpened.signal(); + return S_OK; + } + ).Get()); - ++n; + // When using Bluetooth the asynchronous port opening operation will occasionally + // hang, so we use a timeout. We will be able to remove this when Microsoft + // improves the Bluetooth MIDI stack. + portOpened.wait (2000); } - } - return 0; -} + const String deviceId; + ComSmartPtr& factory; + ComSmartPtr& port; -MidiOutput* MidiOutput::openDevice (int index) -{ - UINT deviceId = MIDI_MAPPER; - const UINT num = midiOutGetNumDevs(); - int n = 0; - String deviceName; + WaitableEvent portOpened { true }; + }; - for (UINT i = 0; i < num; ++i) + struct WinRTInputWrapper : public InputWrapper { - MIDIOUTCAPS mc = { 0 }; + WinRTInputWrapper (WinRTMidiService& service, + MidiInput* const input, + const int index, + MidiInputCallback& cb) + : inputDevice (input), + callback (cb), + concatenator (4096) + { + const ScopedLock lock (service.inputDeviceWatcher->deviceChanges); + + deviceName = service.inputDeviceWatcher->getDeviceNameFromIndex (index); + if (deviceName.isEmpty()) + throw std::runtime_error ("Invalid device index"); + + const auto deviceID = service.inputDeviceWatcher->getDeviceID (deviceName); + if (deviceID.isEmpty()) + throw std::runtime_error ("Device unavailable"); + + OpenMidiPortThread portThread ("Open WinRT MIDI input port", + deviceID, + service.midiInFactory, + midiInPort); + portThread.startThread(); + portThread.waitForThreadToExit (-1); + if (midiInPort == nullptr) + throw std::runtime_error ("Timed out waiting for midi input port creation"); + + startTime = Time::getMillisecondCounterHiRes(); - if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) + HRESULT hr = midiInPort->add_MessageReceived ( + Callback> ( + [this] (IMidiInPort*, IMidiMessageReceivedEventArgs* args) { return midiInMessageReceived (args); } + ).Get(), + &midiInMessageToken); + if (FAILED (hr)) + throw std::runtime_error ("Failed to set midi input callback"); + } + + ~WinRTInputWrapper() { - String name = String (mc.szPname, sizeof (mc.szPname)); + if (midiInMessageToken.value != 0) + midiInPort->remove_MessageReceived (midiInMessageToken); - // use the microsoft sw synth as a default - best not to allow deviceId - // to be MIDI_MAPPER, or else device sharing breaks - if (name.containsIgnoreCase ("microsoft")) - deviceId = i; + midiInPort = nullptr; + } - if (index == n) + void start() override + { + if (!isStarted) { - deviceName = name; - deviceId = i; - break; + concatenator.reset(); + isStarted = true; } + } - ++n; + void stop() override + { + if (isStarted) + { + isStarted = false; + concatenator.reset(); + } } - } - for (int i = MidiOutHandle::activeHandles.size(); --i >= 0;) - { - MidiOutHandle* const han = MidiOutHandle::activeHandles.getUnchecked(i); + String getDeviceName() override + { + return deviceName; + } + + HRESULT midiInMessageReceived (IMidiMessageReceivedEventArgs* args) + { + if (! isStarted) + return S_OK; + + ComSmartPtr message; + HRESULT hr = args->get_Message (message.resetAndGetPointerAddress()); + if (FAILED (hr)) + return hr; + + ComSmartPtr buffer; + hr = message->get_RawData (buffer.resetAndGetPointerAddress()); + if (FAILED (hr)) + return hr; + + ComSmartPtr bufferByteAccess; + hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress()); + if (FAILED (hr)) + return hr; + + uint8_t* bufferData = nullptr; + hr = bufferByteAccess->Buffer (&bufferData); + if (FAILED (hr)) + return hr; + + uint32_t numBytes = 0; + hr = buffer->get_Length (&numBytes); + if (FAILED (hr)) + return hr; + + ABI::Windows::Foundation::TimeSpan timespan; + hr = message->get_Timestamp (×pan); + if (FAILED (hr)) + return hr; + + concatenator.pushMidiData (bufferData, + numBytes, + convertTimeStamp (timespan.Duration), + inputDevice, + callback); + + return S_OK; + } - if (han->deviceId == deviceId) + double convertTimeStamp (int64 timestamp) { - han->refCount++; + const auto millisecondsSinceStart = static_cast (timestamp) / 10000.0; + double t = startTime + millisecondsSinceStart; + + const double now = Time::getMillisecondCounterHiRes(); + if (t > now) + { + if (t > now + 2.0) + startTime -= 1.0; - MidiOutput* const out = new MidiOutput (deviceName); - out->internal = han; - return out; + t = now; + } + + return t * 0.001; } - } - for (int i = 4; --i >= 0;) - { - HMIDIOUT h = 0; - MMRESULT res = midiOutOpen (&h, deviceId, 0, 0, CALLBACK_NULL); + MidiInput* inputDevice; + MidiInputCallback& callback; + String deviceName; + MidiDataConcatenator concatenator; + ComSmartPtr midiInPort; + EventRegistrationToken midiInMessageToken { 0 }; - if (res == MMSYSERR_NOERROR) - { - MidiOutHandle* const han = new MidiOutHandle(); - han->deviceId = deviceId; - han->refCount = 1; - han->handle = h; - MidiOutHandle::activeHandles.add (han); + double startTime = 0; + bool isStarted = false; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTInputWrapper); + }; - MidiOutput* const out = new MidiOutput (deviceName); - out->internal = han; - return out; + struct WinRTOutputWrapper : public OutputWrapper + { + WinRTOutputWrapper (WinRTMidiService& service, const int index) + { + const ScopedLock lock (service.outputDeviceWatcher->deviceChanges); + + deviceName = service.outputDeviceWatcher->getDeviceNameFromIndex (index); + if (deviceName.isEmpty()) + throw std::runtime_error ("Invalid device index"); + + const auto deviceID = service.outputDeviceWatcher->getDeviceID (deviceName); + if (deviceID.isEmpty()) + throw std::runtime_error ("Device unavailable"); + + OpenMidiPortThread portThread ("Open WinRT MIDI output port", + deviceID, + service.midiOutFactory, + midiOutPort); + portThread.startThread(); + portThread.waitForThreadToExit (-1); + if (midiOutPort == nullptr) + throw std::runtime_error ("Timed out waiting for midi output port creation"); + + auto bufferFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Storage_Streams_Buffer[0]); + if (bufferFactory == nullptr) + throw std::runtime_error ("Failed to create output buffer factory"); + + HRESULT hr = bufferFactory->Create (static_cast (256), buffer.resetAndGetPointerAddress()); + if (FAILED (hr)) + throw std::runtime_error ("Failed to create output buffer"); + + hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress()); + if (FAILED (hr)) + throw std::runtime_error ("Failed to get buffer byte access"); + + hr = bufferByteAccess->Buffer (&bufferData); + if (FAILED (hr)) + throw std::runtime_error ("Failed to get buffer data pointer"); } - else if (res == MMSYSERR_ALLOCATED) + + ~WinRTOutputWrapper() {} + + void sendMessageNow (const MidiMessage& message) override { - Sleep (100); + const UINT32 numBytes = message.getRawDataSize(); + HRESULT hr = buffer->put_Length (numBytes); + if (FAILED (hr)) + jassertfalse; + + memcpy_s (bufferData, numBytes, message.getRawData(), numBytes); + + midiOutPort->SendBuffer (buffer); } - else + + String getDeviceName() override { - break; + return deviceName; } + + String deviceName; + ComSmartPtr midiOutPort; + ComSmartPtr buffer; + ComSmartPtr bufferByteAccess; + uint8_t* bufferData = nullptr; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTOutputWrapper); + }; + +public: + WinRTMidiService() + { + if (! WinRTWrapper::getInstance()->isInitialised()) + throw std::runtime_error ("Failed to initialise the WinRT wrapper"); + + midiInFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Devices_Midi_MidiInPort[0]); + if (midiInFactory == nullptr) + throw std::runtime_error ("Failed to create midi in factory"); + + midiOutFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Devices_Midi_MidiOutPort[0]); + if (midiOutFactory == nullptr) + throw std::runtime_error ("Failed to create midi out factory"); + + inputDeviceWatcher = new MidiIODeviceWatcher (midiInFactory); + if (! inputDeviceWatcher->start()) + throw std::runtime_error ("Failed to start midi input device watcher"); + + outputDeviceWatcher = new MidiIODeviceWatcher (midiOutFactory); + if (! outputDeviceWatcher->start()) + throw std::runtime_error ("Failed to start midi output device watcher"); } - return nullptr; -} + ~WinRTMidiService() + { + } -MidiOutput::~MidiOutput() -{ - stopBackgroundThread(); + StringArray getDevices (bool isInput) override + { + return isInput ? inputDeviceWatcher ->getDevices() + : outputDeviceWatcher->getDevices(); + } - MidiOutHandle* const h = static_cast (internal); + int getDefaultDeviceIndex (bool isInput) override + { + return isInput ? inputDeviceWatcher ->getDefaultDeviceIndex() + : outputDeviceWatcher->getDefaultDeviceIndex(); + } - if (MidiOutHandle::activeHandles.contains (h) && --(h->refCount) == 0) + InputWrapper* createInputWrapper (MidiInput* const input, + const int index, + MidiInputCallback* const callback) override { - midiOutClose (h->handle); - MidiOutHandle::activeHandles.removeFirstMatchingValue (h); - delete h; + return new WinRTInputWrapper (*this, input, index, *callback); } -} -void MidiOutput::sendMessageNow (const MidiMessage& message) + OutputWrapper* createOutputWrapper (const int index) override + { + return new WinRTOutputWrapper (*this, index); + } + + ComSmartPtr midiInFactory; + ComSmartPtr midiOutFactory; + + ScopedPointer> inputDeviceWatcher; + ScopedPointer> outputDeviceWatcher; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTMidiService) +}; + +#endif // JUCE_USE_WINRT_MIDI + +//============================================================================== +MidiServiceType* MidiService::getService() { - const MidiOutHandle* const handle = static_cast (internal); + return internal.get(); +} - if (message.getRawDataSize() > 3 || message.isSysEx()) +MidiService::MidiService() +{ + #if JUCE_USE_WINRT_MIDI + try + { + internal = new WinRTMidiService(); + return; + } + catch (std::runtime_error&) { - MIDIHDR h = { 0 }; + } + #endif - h.lpData = (char*) message.getRawData(); - h.dwBytesRecorded = h.dwBufferLength = (DWORD) message.getRawDataSize(); + internal = new WindowsMidiService(); +} - if (midiOutPrepareHeader (handle->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR) - { - MMRESULT res = midiOutLongMsg (handle->handle, &h, sizeof (MIDIHDR)); +//============================================================================== +StringArray MidiInput::getDevices() +{ + return MidiService::getInstance()->getService()->getDevices (true); +} - if (res == MMSYSERR_NOERROR) - { - while ((h.dwFlags & MHDR_DONE) == 0) - Sleep (1); +int MidiInput::getDefaultDeviceIndex() +{ + return MidiService::getInstance()->getService()->getDefaultDeviceIndex (true); +} - int count = 500; // 1 sec timeout +MidiInput::MidiInput (const String& deviceName) + : name (deviceName) +{ +} - while (--count >= 0) - { - res = midiOutUnprepareHeader (handle->handle, &h, sizeof (MIDIHDR)); +MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const callback) +{ + if (callback == nullptr) + return nullptr; - if (res == MIDIERR_STILLPLAYING) - Sleep (2); - else - break; - } - } - } + ScopedPointer in (new MidiInput ("")); + ScopedPointer wrapper; + try + { + wrapper = MidiService::getInstance()->getService()->createInputWrapper (in, index, callback); } - else + catch (std::runtime_error&) { - for (int i = 0; i < 50; ++i) - { - if (midiOutShortMsg (handle->handle, *(unsigned int*) message.getRawData()) != MIDIERR_NOTREADY) - break; + return nullptr; + } - Sleep (1); - } + in->setName (wrapper->getDeviceName()); + in->internal = wrapper.release(); + return in.release(); +} + +MidiInput::~MidiInput() +{ + delete static_cast (internal); +} + +void MidiInput::start() { static_cast (internal)->start(); } +void MidiInput::stop() { static_cast (internal)->stop(); } + +//============================================================================== +StringArray MidiOutput::getDevices() +{ + return MidiService::getInstance()->getService()->getDevices (false); +} + +int MidiOutput::getDefaultDeviceIndex() +{ + return MidiService::getInstance()->getService()->getDefaultDeviceIndex (false); +} + +MidiOutput* MidiOutput::openDevice (const int index) +{ + ScopedPointer wrapper; + try + { + wrapper = MidiService::getInstance()->getService()->createOutputWrapper (index); } + catch (std::runtime_error&) + { + return nullptr; + } + + ScopedPointer out (new MidiOutput (wrapper->getDeviceName())); + out->internal = wrapper.release(); + return out.release(); +} + +MidiOutput::~MidiOutput() +{ + stopBackgroundThread(); + delete static_cast (internal); +} + +void MidiOutput::sendMessageNow (const MidiMessage& message) +{ + auto* const wrapper = static_cast (internal); + wrapper->sendMessageNow (message); } diff --git a/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp b/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp index 165a385908..c23bf62a44 100644 --- a/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp +++ b/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp @@ -233,6 +233,23 @@ struct PhysicalTopologySource::Internal return name.indexOf (" BLOCK") > 0 || name.indexOf (" Block") > 0; } + static String cleanBlocksDeviceName (juce::String name) + { + name = name.trim(); + + if (name.endsWith (" IN)")) + return name.dropLastCharacters (4); + + if (name.endsWith (" OUT)")) + return name.dropLastCharacters (5); + + const int openBracketPosition = name.lastIndexOfChar ('['); + if (openBracketPosition != -1 && name.endsWith ("]")) + return name.dropLastCharacters (name.length() - openBracketPosition); + + return name; + } + struct MidiInputOutputPair { juce::String outputName, inputName; @@ -254,9 +271,10 @@ struct PhysicalTopologySource::Internal pair.inputName = midiInputs[j]; pair.inputIndex = j; + String cleanedInputName = cleanBlocksDeviceName (pair.inputName); for (int i = 0; i < midiOutputs.size(); ++i) { - if (midiOutputs[i].trim() == pair.inputName.trim()) + if (cleanBlocksDeviceName (midiOutputs[i]) == cleanedInputName) { pair.outputName = midiOutputs[i]; pair.outputIndex = i; diff --git a/modules/juce_events/juce_events.cpp b/modules/juce_events/juce_events.cpp index f3c328096c..c429c1df6c 100644 --- a/modules/juce_events/juce_events.cpp +++ b/modules/juce_events/juce_events.cpp @@ -40,8 +40,13 @@ #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 +#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 #define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 +#if JUCE_USE_WINRT_MIDI + #define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1 +#endif + #include "juce_events.h" //============================================================================== @@ -84,6 +89,9 @@ namespace juce #elif JUCE_WINDOWS #include "native/juce_win32_Messaging.cpp" + #if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER + #include "native/juce_win32_WinRTWrapper.cpp" + #endif #elif JUCE_LINUX #include "native/juce_linux_Messaging.cpp" diff --git a/modules/juce_events/juce_events.h b/modules/juce_events/juce_events.h index 2f8d2b5994..a03319584d 100644 --- a/modules/juce_events/juce_events.h +++ b/modules/juce_events/juce_events.h @@ -55,9 +55,12 @@ #pragma once #define JUCE_EVENTS_H_INCLUDED -//============================================================================== #include +#if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER && JUCE_WINDOWS + #include +#endif + namespace juce { @@ -86,8 +89,13 @@ namespace juce #endif -#if JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW && JUCE_WINDOWS - #include "native/juce_win32_HiddenMessageWindow.h" +#if JUCE_WINDOWS + #if JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW + #include "native/juce_win32_HiddenMessageWindow.h" + #endif + #if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER + #include "native/juce_win32_WinRTWrapper.h" + #endif #endif } diff --git a/modules/juce_events/native/juce_win32_WinRTWrapper.cpp b/modules/juce_events/native/juce_win32_WinRTWrapper.cpp new file mode 100644 index 0000000000..2e01926f54 --- /dev/null +++ b/modules/juce_events/native/juce_win32_WinRTWrapper.cpp @@ -0,0 +1,31 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - 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. + + ============================================================================== +*/ + +juce_ImplementSingleton (WinRTWrapper) diff --git a/modules/juce_events/native/juce_win32_WinRTWrapper.h b/modules/juce_events/native/juce_win32_WinRTWrapper.h new file mode 100644 index 0000000000..5e76e82c6e --- /dev/null +++ b/modules/juce_events/native/juce_win32_WinRTWrapper.h @@ -0,0 +1,140 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - 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. + + ============================================================================== +*/ + +#pragma once + +class WinRTWrapper : public DeletedAtShutdown +{ +public: + juce_DeclareSingleton (WinRTWrapper, true) + + class ScopedHString + { + public: + ScopedHString (String str) + { + if (WinRTWrapper::getInstance()->isInitialised()) + WinRTWrapper::getInstance()->createHString (str.toWideCharPointer(), + static_cast (str.length()), + &hstr); + } + + ~ScopedHString() + { + if (WinRTWrapper::getInstance()->isInitialised() && hstr != nullptr) + WinRTWrapper::getInstance()->deleteHString (hstr); + } + + HSTRING get() const noexcept + { + return hstr; + } + + private: + HSTRING hstr = nullptr; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedHString) + }; + + ~WinRTWrapper() + { + if (winRTHandle != nullptr) + ::FreeLibrary (winRTHandle); + } + + String hStringToString (HSTRING hstr) + { + const wchar_t* str = nullptr; + if (isInitialised()) + { + str = getHStringRawBuffer (hstr, nullptr); + if (str != nullptr) + return String (str); + } + + return {}; + } + + bool isInitialised() const noexcept + { + return initialised; + } + + template + ComSmartPtr getWRLFactory (const wchar_t* runtimeClassID) + { + ComSmartPtr comPtr; + + if (isInitialised()) + { + ScopedHString classID (runtimeClassID); + if (classID.get() != nullptr) + roGetActivationFactory (classID.get(), __uuidof (ComClass), (void**) comPtr.resetAndGetPointerAddress()); + } + + return comPtr; + } + +private: + WinRTWrapper() + { + winRTHandle = ::LoadLibraryA ("api-ms-win-core-winrt-l1-1-0"); + if (winRTHandle == nullptr) + return; + + roInitialize = (RoInitializeFuncPtr) ::GetProcAddress (winRTHandle, "RoInitialize"); + createHString = (WindowsCreateStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsCreateString"); + deleteHString = (WindowsDeleteStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsDeleteString"); + getHStringRawBuffer = (WindowsGetStringRawBufferFuncPtr) ::GetProcAddress (winRTHandle, "WindowsGetStringRawBuffer"); + roGetActivationFactory = (RoGetActivationFactoryFuncPtr) ::GetProcAddress (winRTHandle, "RoGetActivationFactory"); + + if (roInitialize == nullptr || createHString == nullptr || deleteHString == nullptr + || getHStringRawBuffer == nullptr || roGetActivationFactory == nullptr) + return; + + HRESULT status = roInitialize (1); + initialised = ! (status != S_OK && status != S_FALSE && status != 0x80010106L); + } + + HMODULE winRTHandle = nullptr; + bool initialised = false; + + typedef HRESULT (WINAPI* RoInitializeFuncPtr) (int); + typedef HRESULT (WINAPI* WindowsCreateStringFuncPtr) (LPCWSTR, UINT32, HSTRING*); + typedef HRESULT (WINAPI* WindowsDeleteStringFuncPtr) (HSTRING); + typedef PCWSTR (WINAPI* WindowsGetStringRawBufferFuncPtr) (HSTRING, UINT32*); + typedef HRESULT (WINAPI* RoGetActivationFactoryFuncPtr) (HSTRING, REFIID, void**); + + RoInitializeFuncPtr roInitialize = nullptr; + WindowsCreateStringFuncPtr createHString = nullptr; + WindowsDeleteStringFuncPtr deleteHString = nullptr; + WindowsGetStringRawBufferFuncPtr getHStringRawBuffer = nullptr; + RoGetActivationFactoryFuncPtr roGetActivationFactory = nullptr; +};