Created
April 14, 2021 15:38
-
-
Save vberthiaume/3a145aba788bff0b67d84895897cfa28 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <JuceHeader.h> | |
class BluetoothMidiSelectorWindowHelper; | |
/** This file is a copy of the juce class BluetoothMidiDevicePairingDialogue, adapted to bring the existing window to the front if open() is called multiple times in a row. | |
*/ | |
class UniqueBluetoothMidiDevicePairingDialogue | |
{ | |
public: | |
/** Opens the Bluetooth MIDI pairing dialogue, if it is available. Multiple calls to this will bring the existing dialog to the front if it wasn't manually closed by the user. | |
@param btWindowBounds The bounds of the bluetooth window that will | |
be opened. The dialog itself is opened by the OS so cannot | |
be customised by JUCE. | |
@return true if the dialogue was opened, false on error. | |
@see ModalComponentManager::Callback | |
*/ | |
static bool open (Rectangle<int>* btWindowBounds = nullptr); | |
/** Checks if a Bluetooth MIDI pairing dialogue is available on this | |
platform. | |
On iOS, this will be true for iOS versions 8.0 and higher. | |
On Android, this will be true only for Android SDK versions 23 and | |
higher, and additionally only if the device itself supports MIDI | |
over Bluetooth. | |
On desktop platforms, this will typically be false as the bluetooth | |
pairing is not done inside the app but by other means. | |
@return true if the Bluetooth MIDI pairing dialogue is available, | |
false otherwise. | |
*/ | |
static bool isAvailable(); | |
static BluetoothMidiSelectorWindowHelper* windowHelper; | |
static bool bleDialogOpened; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "UniqueBluetoothMidiDevicePairingDialogue.h" | |
#if defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 | |
#define Point JucePoint | |
#define Component JuceComponent | |
#import <Foundation/Foundation.h> | |
#undef Point | |
#undef Component | |
#import <objc/objc-runtime.h> | |
#import <juce_core/native/juce_osx_ObjCHelpers.h> | |
#import <CoreAudioKit/CABTLEMIDIWindowController.h> | |
//============================================================================== | |
class BluetoothMidiPairingWindowClass : public ObjCClass<NSObject> | |
{ | |
public: | |
struct Callbacks | |
{ | |
std::unique_ptr<ModalComponentManager::Callback> modalExit; | |
std::function<void()> windowClosed; | |
}; | |
BluetoothMidiPairingWindowClass() : ObjCClass<NSObject> ("JUCEBluetoothMidiPairingWindowClass_") | |
{ | |
addIvar<Callbacks*> ("callbacks"); | |
addIvar<CABTLEMIDIWindowController*> ("controller"); | |
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |
addMethod (@selector (initWithCallbacks:), initWithCallbacks, "@@:^v"); | |
addMethod (@selector (show:), show, "v@:^v"); | |
addMethod (@selector (receivedWindowWillClose:), receivedWindowWillClose, "v@:^v"); | |
addMethod (@selector (orderFront), orderFront, "v@:"); | |
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |
addMethod (@selector (dealloc), dealloc, "v@:"); | |
addMethod (@selector (close), close, "v@:"); | |
registerClass(); | |
} | |
private: | |
static CABTLEMIDIWindowController* getController (id self) | |
{ | |
return getIvar<CABTLEMIDIWindowController*> (self, "controller"); | |
} | |
static id initWithCallbacks (id self, SEL, Callbacks* cbs) | |
{ | |
self = sendSuperclassMessage<id> (self, @selector (init)); | |
object_setInstanceVariable (self, "callbacks", cbs); | |
object_setInstanceVariable (self, "controller", [CABTLEMIDIWindowController new]); | |
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |
[[NSNotificationCenter defaultCenter] addObserver: self | |
selector: @selector (receivedWindowWillClose:) | |
name: @"NSWindowWillCloseNotification" | |
object: [getController (self) window]]; | |
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |
return self; | |
} | |
static void dealloc (id self, SEL) | |
{ | |
[getController (self) release]; | |
sendSuperclassMessage<void> (self, @selector (dealloc)); | |
} | |
static void close (id self, SEL) | |
{ | |
[getController (self).window close]; | |
} | |
static void orderFront (id self, SEL) | |
{ | |
[getController (self).window orderFrontRegardless]; | |
} | |
static void show (id self, SEL, Rectangle<int>* 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]; | |
} | |
//so this is called by the messages manage when the user closes the window | |
static void receivedWindowWillClose (id self, SEL, NSNotification*) | |
{ | |
[[NSNotificationCenter defaultCenter] removeObserver: self]; | |
auto* cbs = getIvar<Callbacks*> (self, "callbacks"); | |
if (cbs->modalExit != nullptr) | |
cbs->modalExit->modalStateFinished (0); | |
cbs->windowClosed(); | |
} | |
}; | |
class BluetoothMidiSelectorWindowHelper: public DeletedAtShutdown | |
{ | |
public: | |
BluetoothMidiSelectorWindowHelper (ModalComponentManager::Callback* exitCallback, | |
Rectangle<int>* bounds) | |
{ | |
std::unique_ptr<ModalComponentManager::Callback> exitCB (exitCallback); | |
static BluetoothMidiPairingWindowClass cls; | |
window.reset (cls.createInstance()); | |
WeakReference<BluetoothMidiSelectorWindowHelper> safeThis (this); | |
auto deletionCB = [=] | |
{ | |
if (auto* t = safeThis.get()) | |
delete t; | |
}; | |
callbacks.reset (new BluetoothMidiPairingWindowClass::Callbacks { std::move (exitCB), std::move (deletionCB) }); | |
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |
[window.get() performSelector: @selector (initWithCallbacks:) | |
withObject: (id) callbacks.get()]; | |
[window.get() performSelector: @selector (show:) | |
withObject: (id) bounds]; | |
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |
} | |
void close() | |
{ | |
[window.get() performSelector: @selector (close)]; | |
} | |
void orderFront() | |
{ | |
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |
[window.get() performSelector: @selector (orderFront)]; | |
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |
} | |
private: | |
std::unique_ptr<NSObject, NSObjectDeleter> window; | |
std::unique_ptr<BluetoothMidiPairingWindowClass::Callbacks> callbacks; | |
JUCE_DECLARE_WEAK_REFERENCEABLE (BluetoothMidiSelectorWindowHelper) | |
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorWindowHelper) | |
}; | |
//============================================================================== | |
BluetoothMidiSelectorWindowHelper* UniqueBluetoothMidiDevicePairingDialogue::windowHelper = nullptr; | |
bool UniqueBluetoothMidiDevicePairingDialogue::bleDialogOpened = false; | |
bool UniqueBluetoothMidiDevicePairingDialogue::open (Rectangle<int>* bounds) | |
{ | |
if (bleDialogOpened) | |
{ | |
windowHelper->orderFront(); | |
return true; | |
} | |
const auto cb { ModalCallbackFunction::create ([] (int) { UniqueBluetoothMidiDevicePairingDialogue::bleDialogOpened = false; } )}; | |
windowHelper = new BluetoothMidiSelectorWindowHelper (cb, bounds); | |
bleDialogOpened = true; | |
return true; | |
} | |
bool UniqueBluetoothMidiDevicePairingDialogue::isAvailable() | |
{ | |
return true; | |
} | |
#else | |
bool UniqueBluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback, Rectangle<int>*) | |
{ | |
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback); | |
// 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; | |
} | |
bool UniqueBluetoothMidiDevicePairingDialogue::isAvailable() | |
{ | |
return false; | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment