From 0828977e5b8fe49d4a39e6b51c7c2e238c0c7a66 Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Fri, 29 Mar 2019 16:39:55 +0000 Subject: [PATCH] macOS: Added a Bluetooth MIDI pairing dialog --- .../juce_BluetoothMidiDevicePairingDialogue.h | 10 +- modules/juce_audio_utils/juce_audio_utils.cpp | 1 + modules/juce_audio_utils/juce_audio_utils.h | 2 +- ..._mac_BluetoothMidiDevicePairingDialogue.mm | 157 +++++++++++++++++- 4 files changed, 162 insertions(+), 8 deletions(-) diff --git a/modules/juce_audio_utils/gui/juce_BluetoothMidiDevicePairingDialogue.h b/modules/juce_audio_utils/gui/juce_BluetoothMidiDevicePairingDialogue.h index 647c6472a2..a0aa70d039 100644 --- a/modules/juce_audio_utils/gui/juce_BluetoothMidiDevicePairingDialogue.h +++ b/modules/juce_audio_utils/gui/juce_BluetoothMidiDevicePairingDialogue.h @@ -37,11 +37,11 @@ namespace juce Only after a Bluetooth MIDI device has been paired will its MIDI ports be available through JUCE's MidiInput and MidiOutput classes. - This dialogue is currently only available on iOS and Android. On OSX, - you should instead pair Bluetooth MIDI devices using the "Audio MIDI Setup" - app (located in /Applications/Utilities). On Windows, you should use - the system settings. On Linux, Bluetooth MIDI devices are currently not - supported. + This dialogue is currently only available on macOS targetting versions 10.11+, + iOS and Android. When targeting older versions of macOS you should instead + pair Bluetooth MIDI devices using the "Audio MIDI Setup" app (located in + /Applications/Utilities). On Windows, you should use the system settings. On + Linux, Bluetooth MIDI devices are currently not supported. @tags{Audio} */ diff --git a/modules/juce_audio_utils/juce_audio_utils.cpp b/modules/juce_audio_utils/juce_audio_utils.cpp index fa77081ea7..95ef694ae7 100644 --- a/modules/juce_audio_utils/juce_audio_utils.cpp +++ b/modules/juce_audio_utils/juce_audio_utils.cpp @@ -43,6 +43,7 @@ #if JUCE_MAC #import + #import #elif JUCE_WINDOWS #if JUCE_USE_CDBURNER /* You'll need the Platform SDK for these headers - if you don't have it and don't diff --git a/modules/juce_audio_utils/juce_audio_utils.h b/modules/juce_audio_utils/juce_audio_utils.h index afcee9576a..01ffce901b 100644 --- a/modules/juce_audio_utils/juce_audio_utils.h +++ b/modules/juce_audio_utils/juce_audio_utils.h @@ -42,7 +42,7 @@ license: GPL/Commercial dependencies: juce_gui_extra, juce_audio_processors, juce_audio_formats, juce_audio_devices - OSXFrameworks: DiscRecording + OSXFrameworks: CoreAudioKit DiscRecording iOSFrameworks: CoreAudioKit END_JUCE_MODULE_DECLARATION diff --git a/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm b/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm index a1d30f23d3..eb1d1a1389 100644 --- a/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm +++ b/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm @@ -27,12 +27,163 @@ namespace juce { +#if defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 + +//============================================================================== +class BluetoothMidiPairingWindowClass : public ObjCClass +{ +public: + struct Callbacks + { + std::unique_ptr modalExit; + std::function windowClosed; + }; + + BluetoothMidiPairingWindowClass() : ObjCClass ("JUCEBluetoothMidiPairingWindowClass_") + { + addIvar ("callbacks"); + addIvar ("controller"); + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wundeclared-selector" + addMethod (@selector (initWithCallbacks:), initWithCallbacks, "@@:^v"); + addMethod (@selector (show:), show, "v@:^v"); + addMethod (@selector (receivedWindowWillClose:), receivedWindowWillClose, "v@:^v"); + #pragma clang diagnostic pop + + addMethod (@selector (dealloc), dealloc, "v@:"); + + registerClass(); + } + +private: + static CABTLEMIDIWindowController* getController (id self) + { + return getIvar (self, "controller"); + } + + static id initWithCallbacks (id self, SEL, Callbacks* cbs) + { + self = sendSuperclassMessage (self, @selector (init)); + + object_setInstanceVariable (self, "callbacks", cbs); + object_setInstanceVariable (self, "controller", [CABTLEMIDIWindowController new]); + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wundeclared-selector" + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector (receivedWindowWillClose:) + name: @"NSWindowWillCloseNotification" + object: [getController (self) window]]; + #pragma clang diagnostic pop + + return self; + } + + static void dealloc (id self, SEL) + { + [getController (self) release]; + + sendSuperclassMessage (self, @selector (dealloc)); + } + + static void show (id self, SEL, Rectangle* bounds) + { + if (bounds != nullptr) + { + auto nsBounds = makeNSRect (*bounds); + + auto mainScreenHeight = [] + { + if ([[NSScreen screens] count] == 0) + return (CGFloat) 0.0f; + + return [[[NSScreen screens] objectAtIndex: 0] frame].size.height; + }(); + + nsBounds.origin.y = mainScreenHeight - (nsBounds.origin.y + nsBounds.size.height); + + [getController (self).window setFrame: nsBounds + display: YES]; + } + + [getController (self) showWindow: nil]; + } + + static void receivedWindowWillClose (id self, SEL, NSNotification*) + { + [[NSNotificationCenter defaultCenter] removeObserver: self]; + + auto* cbs = getIvar (self, "callbacks"); + + if (cbs->modalExit != nullptr) + cbs->modalExit->modalStateFinished (0); + + cbs->windowClosed(); + } +}; + +class BluetoothMidiSelectorWindowHelper : public DeletedAtShutdown +{ +public: + BluetoothMidiSelectorWindowHelper (ModalComponentManager::Callback* exitCallback, + Rectangle* bounds) + { + std::unique_ptr exitCB (exitCallback); + + static BluetoothMidiPairingWindowClass cls; + window.reset (cls.createInstance()); + + WeakReference safeThis (this); + + auto deletionCB = [=] + { + if (auto* t = safeThis.get()) + delete t; + }; + + callbacks.reset (new BluetoothMidiPairingWindowClass::Callbacks { std::move (exitCB), + std::move (deletionCB) }); + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wundeclared-selector" + [window.get() performSelector: @selector (initWithCallbacks:) + withObject: (id) callbacks.get()]; + [window.get() performSelector: @selector (show:) + withObject: (id) bounds]; + #pragma clang diagnostic pop + } + +private: + std::unique_ptr window; + std::unique_ptr callbacks; + + JUCE_DECLARE_WEAK_REFERENCEABLE (BluetoothMidiSelectorWindowHelper) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorWindowHelper) +}; + +//============================================================================== +bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback, + Rectangle* bounds) +{ + new BluetoothMidiSelectorWindowHelper (exitCallback, bounds); + return true; +} + +bool BluetoothMidiDevicePairingDialogue::isAvailable() +{ + return true; +} + +#else + bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback, Rectangle*) { std::unique_ptr cb (exitCallback); - // Do not call this on OSX. Instead, you should pair Bluetooth MIDI devices - // using the "Audio MIDI Setup" app (located in /Applications/Utilities). + // This functionality is unavailable when targetting OSX < 10.11. Instead, + // you should pair Bluetooth MIDI devices using the "Audio MIDI Setup" app + // (located in /Applications/Utilities). jassertfalse; return false; } @@ -42,4 +193,6 @@ bool BluetoothMidiDevicePairingDialogue::isAvailable() return false; } +#endif + } // namespace juce