1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

added the AppleRemoteDevice class

This commit is contained in:
jules 2007-07-25 16:55:55 +00:00
parent eabf835cb7
commit 2f7be7bcbe
5 changed files with 621 additions and 206 deletions

View file

@ -31,6 +31,10 @@
#include "../../../src/juce_core/basics/juce_StandardHeader.h"
#include <Carbon/Carbon.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <fnmatch.h>
#if JUCE_OPENGL
@ -2997,6 +3001,242 @@ const int KeyPress::stopKey = 0x30001;
const int KeyPress::fastForwardKey = 0x30002;
const int KeyPress::rewindKey = 0x30003;
//==============================================================================
AppleRemoteDevice::AppleRemoteDevice()
: device (0),
queue (0),
remoteId (0)
{
}
AppleRemoteDevice::~AppleRemoteDevice()
{
stop();
}
static io_object_t getAppleRemoteDevice() throw()
{
CFMutableDictionaryRef dict = IOServiceMatching ("AppleIRController");
io_iterator_t iter = 0;
io_object_t iod = 0;
if (IOServiceGetMatchingServices (kIOMasterPortDefault, dict, &iter) == kIOReturnSuccess
&& iter != 0)
{
iod = IOIteratorNext (iter);
}
IOObjectRelease (iter);
return iod;
}
static bool createAppleRemoteInterface (io_object_t iod, void** device) throw()
{
jassert (*device == 0);
io_name_t classname;
if (IOObjectGetClass (iod, classname) == kIOReturnSuccess)
{
IOCFPlugInInterface** cfPlugInInterface = 0;
SInt32 score = 0;
if (IOCreatePlugInInterfaceForService (iod,
kIOHIDDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&cfPlugInInterface,
&score) == kIOReturnSuccess)
{
HRESULT hr = (*cfPlugInInterface)->QueryInterface (cfPlugInInterface,
CFUUIDGetUUIDBytes (kIOHIDDeviceInterfaceID),
device);
(void) hr;
(*cfPlugInInterface)->Release (cfPlugInInterface);
}
}
return *device != 0;
}
bool AppleRemoteDevice::start (const bool inExclusiveMode) throw()
{
if (queue != 0)
return true;
stop();
bool result = false;
io_object_t iod = getAppleRemoteDevice();
if (iod != 0)
{
if (createAppleRemoteInterface (iod, &device) && open (inExclusiveMode))
result = true;
else
stop();
IOObjectRelease (iod);
}
return result;
}
void AppleRemoteDevice::stop() throw()
{
if (queue != 0)
{
(*(IOHIDQueueInterface**) queue)->stop ((IOHIDQueueInterface**) queue);
(*(IOHIDQueueInterface**) queue)->dispose ((IOHIDQueueInterface**) queue);
(*(IOHIDQueueInterface**) queue)->Release ((IOHIDQueueInterface**) queue);
queue = 0;
}
if (device != 0)
{
(*(IOHIDDeviceInterface**) device)->close ((IOHIDDeviceInterface**) device);
(*(IOHIDDeviceInterface**) device)->Release ((IOHIDDeviceInterface**) device);
device = 0;
}
}
static void appleRemoteQueueCallback (void* const target, const IOReturn result, void*, void*)
{
if (result == kIOReturnSuccess)
((AppleRemoteDevice*) target)->handleCallbackInternal();
}
bool AppleRemoteDevice::open (const bool openInExclusiveMode) throw()
{
#if ! MACOS_10_2_OR_EARLIER
Array <int> cookies;
CFArrayRef elements;
IOHIDDeviceInterface122** const device122 = (IOHIDDeviceInterface122**) device;
if ((*device122)->copyMatchingElements (device122, 0, &elements) != kIOReturnSuccess)
return false;
for (int i = 0; i < CFArrayGetCount (elements); ++i)
{
CFDictionaryRef element = (CFDictionaryRef) CFArrayGetValueAtIndex (elements, i);
// get the cookie
CFTypeRef object = CFDictionaryGetValue (element, CFSTR (kIOHIDElementCookieKey));
if (object == 0 || CFGetTypeID (object) != CFNumberGetTypeID())
continue;
long number;
if (! CFNumberGetValue ((CFNumberRef) object, kCFNumberLongType, &number))
continue;
cookies.add ((int) number);
}
CFRelease (elements);
if ((*(IOHIDDeviceInterface**) device)
->open ((IOHIDDeviceInterface**) device,
openInExclusiveMode ? kIOHIDOptionsTypeSeizeDevice
: kIOHIDOptionsTypeNone) == KERN_SUCCESS)
{
queue = (*(IOHIDDeviceInterface**) device)->allocQueue ((IOHIDDeviceInterface**) device);
if (queue != 0)
{
(*(IOHIDQueueInterface**) queue)->create ((IOHIDQueueInterface**) queue, 0, 12);
for (int i = 0; i < cookies.size(); ++i)
{
IOHIDElementCookie cookie = (IOHIDElementCookie) cookies.getUnchecked(i);
(*(IOHIDQueueInterface**) queue)->addElement ((IOHIDQueueInterface**) queue, cookie, 0);
}
CFRunLoopSourceRef eventSource;
if ((*(IOHIDQueueInterface**) queue)
->createAsyncEventSource ((IOHIDQueueInterface**) queue, &eventSource) == KERN_SUCCESS)
{
if ((*(IOHIDQueueInterface**) queue)->setEventCallout ((IOHIDQueueInterface**) queue,
appleRemoteQueueCallback, this, 0) == KERN_SUCCESS)
{
CFRunLoopAddSource (CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
(*(IOHIDQueueInterface**) queue)->start ((IOHIDQueueInterface**) queue);
return true;
}
}
}
}
#endif
return false;
}
void AppleRemoteDevice::handleCallbackInternal()
{
int totalValues = 0;
AbsoluteTime nullTime = { 0, 0 };
char cookies [12];
int numCookies = 0;
while (numCookies < numElementsInArray (cookies))
{
IOHIDEventStruct e;
if ((*(IOHIDQueueInterface**) queue)->getNextEvent ((IOHIDQueueInterface**) queue, &e, nullTime, 0) != kIOReturnSuccess)
break;
if ((int) e.elementCookie == 19)
{
remoteId = e.value;
buttonPressed (switched, false);
}
else
{
totalValues += e.value;
cookies [numCookies++] = (char) (pointer_sized_int) e.elementCookie;
}
}
cookies [numCookies++] = 0;
static const char buttonPatterns[] =
{
14, 7, 6, 5, 14, 7, 6, 5, 0,
14, 8, 6, 5, 14, 8, 6, 5, 0,
14, 12, 11, 6, 5, 0,
14, 13, 11, 6, 5, 0,
14, 9, 6, 5, 14, 9, 6, 5, 0,
14, 10, 6, 5, 14, 10, 6, 5, 0,
14, 6, 5, 4, 2, 0,
14, 6, 5, 3, 2, 0,
14, 6, 5, 14, 6, 5, 0,
18, 14, 6, 5, 18, 14, 6, 5, 0,
19, 0
};
int buttonNum = (int) menuButton;
int i = 0;
while (i < numElementsInArray (buttonPatterns))
{
if (strcmp (cookies, buttonPatterns + i) == 0)
{
buttonPressed ((ButtonType) buttonNum, totalValues > 0);
break;
}
i += strlen (buttonPatterns + i) + 1;
++buttonNum;
}
}
//==============================================================================
#if JUCE_OPENGL

View file

@ -6,6 +6,7 @@
==============================================================================
Changelist for version 1.44
- new Mac-only class: AppleRemoteDevice, which lets you grab and listen for events from your Apple remote control.
- change to the keyPressed() and keyStateChanged() callbacks in Component and KeyListener. These used to be void, but they now return a bool to indicate whether the key event was needed or not. Any existing code you've got will break in the compiler, so just change it to return true if the key was used, or false to allow the event to be passed up to the next component in the chain. (This change is a better architecture than before, and was also needed so that plugins can allow unused key events to be passed on to the host application)
- swapped the look and feel classes around, so that the basic LookAndFeel class is now what used to be the "shiny" one. The ShinyLookAndFeel class has been removed, and for that old fashioned look, I've added an OldSchoolLookAndFeel that you can use if you need the original L+F. This means that any custom looks that you were using may need to change their base class.
- changed the MouseEvent structure so that it now contains a pointer to the event component and also the original component.

View file

@ -1024,6 +1024,85 @@ public:
}
};
#if JUCE_MAC
//==============================================================================
/** This pops open a dialog box and waits for you to press keys on your Apple Remote,
which it describes in the box.
*/
class AppleRemoteTestWindow : public AlertWindow,
public AppleRemoteDevice
{
public:
AppleRemoteTestWindow()
: AlertWindow ("Apple Remote Control Test!",
"If you've got an Apple Remote, press some buttons now...",
AlertWindow::NoIcon)
{
addButton (T("done"), 0);
// (To open the device in non-exclusive mode, pass 'false' in here)..
if (! start (true))
setMessage ("Couldn't open the remote control device!");
}
~AppleRemoteTestWindow()
{
stop();
}
void buttonPressed (const ButtonType buttonId, const bool isDown)
{
String desc;
switch (buttonId)
{
case menuButton:
desc = "menu button (short)";
break;
case playButton:
desc = "play button";
break;
case plusButton:
desc = "plus button";
break;
case minusButton:
desc = "minus button";
break;
case rightButton:
desc = "right button (short)";
break;
case leftButton:
desc = "left button (short)";
break;
case rightButton_Long:
desc = "right button (long)";
break;
case leftButton_Long:
desc = "left button (long)";
break;
case menuButton_Long:
desc = "menu button (long)";
break;
case playButtonSleepMode:
desc = "play (sleep mode)";
break;
case switched:
desc = "remote switched";
break;
}
if (isDown)
desc << " -- [down]";
else
desc << " -- [up]";
setMessage (desc);
}
};
#endif
//==============================================================================
const int numGroups = 4;
@ -1085,67 +1164,71 @@ public:
else if (button == menuButton)
{
PopupMenu m;
m.addItem (1, T("normal item"));
m.addItem (2, T("disabled item"), false);
m.addItem (3, T("ticked item"), true, true);
m.addColouredItem (4, T("coloured item"), Colours::green);
m.addItem (1, T("Normal item"));
m.addItem (2, T("Disabled item"), false);
m.addItem (3, T("Ticked item"), true, true);
m.addColouredItem (4, T("Coloured item"), Colours::green);
m.addSeparator();
m.addCustomItem (5, new CustomMenuComponent());
m.addSeparator();
PopupMenu tabsMenu;
tabsMenu.addItem (1001, T("show tabs at the top"), true, tabs->getOrientation() == TabbedButtonBar::TabsAtTop);
tabsMenu.addItem (1002, T("show tabs at the bottom"), true, tabs->getOrientation() == TabbedButtonBar::TabsAtBottom);
tabsMenu.addItem (1003, T("show tabs at the left"), true, tabs->getOrientation() == TabbedButtonBar::TabsAtLeft);
tabsMenu.addItem (1004, T("show tabs at the right"), true, tabs->getOrientation() == TabbedButtonBar::TabsAtRight);
m.addSubMenu (T("tab position"), tabsMenu);
tabsMenu.addItem (1001, T("Show tabs at the top"), true, tabs->getOrientation() == TabbedButtonBar::TabsAtTop);
tabsMenu.addItem (1002, T("Show tabs at the bottom"), true, tabs->getOrientation() == TabbedButtonBar::TabsAtBottom);
tabsMenu.addItem (1003, T("Show tabs at the left"), true, tabs->getOrientation() == TabbedButtonBar::TabsAtLeft);
tabsMenu.addItem (1004, T("Show tabs at the right"), true, tabs->getOrientation() == TabbedButtonBar::TabsAtRight);
m.addSubMenu (T("Tab position"), tabsMenu);
m.addSeparator();
PopupMenu dialogMenu;
dialogMenu.addItem (100, T("show a plain alert-window..."));
dialogMenu.addItem (101, T("show an alert-window with a 'warning' icon..."));
dialogMenu.addItem (102, T("show an alert-window with an 'info' icon..."));
dialogMenu.addItem (103, T("show an alert-window with a 'question' icon..."));
dialogMenu.addItem (100, T("Show a plain alert-window..."));
dialogMenu.addItem (101, T("Show an alert-window with a 'warning' icon..."));
dialogMenu.addItem (102, T("Show an alert-window with an 'info' icon..."));
dialogMenu.addItem (103, T("Show an alert-window with a 'question' icon..."));
dialogMenu.addSeparator();
dialogMenu.addItem (110, T("show an ok/cancel alert-window..."));
dialogMenu.addItem (110, T("Show an ok/cancel alert-window..."));
dialogMenu.addSeparator();
dialogMenu.addItem (111, T("show an alert-window with some extra components..."));
dialogMenu.addItem (111, T("Show an alert-window with some extra components..."));
dialogMenu.addSeparator();
dialogMenu.addItem (112, T("show a ThreadWithProgressWindow demo..."));
dialogMenu.addItem (112, T("Show a ThreadWithProgressWindow demo..."));
m.addSubMenu (T("AlertWindow demonstrations"), dialogMenu);
dialogMenu.addSeparator();
m.addSeparator();
m.addItem (120, T("show a colour selector demo..."));
m.addItem (120, T("Show a colour selector demo..."));
m.addSeparator();
dialogMenu.addSeparator();
#if JUCE_MAC
m.addItem (140, T("Run the Apple Remote Control test..."));
m.addSeparator();
#endif
PopupMenu nativeFileChoosers;
nativeFileChoosers.addItem (121, T("'load' file browser..."));
nativeFileChoosers.addItem (124, T("'load' file browser with an image file preview..."));
nativeFileChoosers.addItem (122, T("'save' file browser..."));
nativeFileChoosers.addItem (123, T("choose directory file browser..."));
nativeFileChoosers.addItem (121, T("'Load' file browser..."));
nativeFileChoosers.addItem (124, T("'Load' file browser with an image file preview..."));
nativeFileChoosers.addItem (122, T("'Save' file browser..."));
nativeFileChoosers.addItem (123, T("'Choose directory' file browser..."));
PopupMenu juceFileChoosers;
juceFileChoosers.addItem (131, T("'load' file browser..."));
juceFileChoosers.addItem (134, T("'load' file browser with an image file preview..."));
juceFileChoosers.addItem (132, T("'save' file browser..."));
juceFileChoosers.addItem (133, T("choose directory file browser..."));
juceFileChoosers.addItem (131, T("'Load' file browser..."));
juceFileChoosers.addItem (134, T("'Load' file browser with an image file preview..."));
juceFileChoosers.addItem (132, T("'Save' file browser..."));
juceFileChoosers.addItem (133, T("'Choose directory' file browser..."));
PopupMenu fileChoosers;
fileChoosers.addSubMenu (T("Operating system dialogs"), nativeFileChoosers);
fileChoosers.addSubMenu (T("Juce dialogs"), juceFileChoosers);
m.addSubMenu (T("file chooser dialogs"), fileChoosers);
m.addSubMenu (T("File chooser dialogs"), fileChoosers);
int result = m.showAt (menuButton);
@ -1232,6 +1315,13 @@ public:
// method causes the loop to exit.
colourDialog.runModalLoop();
}
else if (result == 140)
{
#if JUCE_MAC
AppleRemoteTestWindow test;
test.runModalLoop();
#endif
}
else if (result >= 121 && result < 139)
{
const bool useNativeVersion = result < 130;

View file

@ -150,4 +150,88 @@ public:
};
#if JUCE_MAC
//==============================================================================
/**
A wrapper class for picking up events from an Apple IR remote control device.
To use it, just create a subclass of this class, implementing the buttonPressed()
callback, then call start() and stop() to start or stop receiving events.
*/
class JUCE_API AppleRemoteDevice
{
public:
//==============================================================================
AppleRemoteDevice();
~AppleRemoteDevice();
//==============================================================================
/** The set of buttons that may be pressed.
@see buttonPressed
*/
enum ButtonType
{
menuButton = 0, /**< The menu button (if it's held for a short time). */
playButton, /**< The play button. */
plusButton, /**< The plus or volume-up button. */
minusButton, /**< The minus or volume-down button. */
rightButton, /**< The right button (if it's held for a short time). */
leftButton, /**< The left button (if it's held for a short time). */
rightButton_Long, /**< The right button (if it's held for a long time). */
leftButton_Long, /**< The menu button (if it's held for a long time). */
menuButton_Long, /**< The menu button (if it's held for a long time). */
playButtonSleepMode,
switched
};
//==============================================================================
/** Override this method to receive the callback about a button press.
The callback will happen on the application's message thread.
Some buttons trigger matching up and down events, in which the isDown parameter
will be true and then false. Others only send a single event when the
button is pressed.
*/
virtual void buttonPressed (const ButtonType buttonId, const bool isDown) = 0;
//==============================================================================
/** Starts the device running and responding to events.
Returns true if it managed to open the device.
@param inExclusiveMode if true, the remote will be grabbed exclusively for this app,
and will not be available to any other part of the system. If
false, it will be shared with other apps.
@see stop
*/
bool start (const bool inExclusiveMode) throw();
/** Stops the device running.
@see start
*/
void stop() throw();
/** Returns the ID number of the remote, if it has sent one.
*/
int getRemoteId() const throw() { return remoteId; }
//==============================================================================
juce_UseDebuggingNewOperator
/** @internal */
void handleCallbackInternal();
private:
void* device;
void* queue;
int remoteId;
bool open (const bool openInExclusiveMode) throw();
};
#endif
#endif // __JUCE_PLATFORMUTILITIES_JUCEHEADER__