Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vberthiaume/3a145aba788bff0b67d84895897cfa28 to your computer and use it in GitHub Desktop.
Save vberthiaume/3a145aba788bff0b67d84895897cfa28 to your computer and use it in GitHub Desktop.
#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;
};
#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