Permalink
Browse files

AppleTV 2.3 remote support, thanks to Emlyn Bolton. Refs #6422.

Re-structured a bit from supplied patch to remove blocks of if (usingNewATV),
a few less magic numbers - hopefully a little easier to understand/maintain.
Untested on an AppleTV. Will let bake a while in HEAD before backporting.
  • Loading branch information...
1 parent 6bf25f0 commit 596f98b086aa457d90d4ce376be7c059ba9680a5 @NigelPearson NigelPearson committed Oct 20, 2011
Showing with 277 additions and 5 deletions.
  1. +259 −3 mythtv/libs/libmythui/AppleRemote.cpp
  2. +18 −2 mythtv/libs/libmythui/AppleRemote.h
@@ -6,6 +6,7 @@
#include <stdlib.h>
#include <ctype.h>
#include <sys/errno.h>
+#include <sys/sysctl.h> // for sysctlbyname
#include <sysexits.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
@@ -14,6 +15,7 @@
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h> // for Gestalt
#include <sstream>
@@ -23,9 +25,22 @@ AppleRemote* AppleRemote::_instance = 0;
#define REMOTE_SWITCH_COOKIE 19
+#define REMOTE_COOKIE_STR "19_"
+#define ATV_COOKIE_STR "17_9_280_"
+#define LONG_PRESS_COUNT 10
+#define KEY_RESPONSE_TIME 150 /* msecs before we send a key up event */
#define LOC QString("AppleRemote::")
+typedef struct _ATV_IR_EVENT
+{
+ UInt32 time_ms32;
+ UInt32 time_ls32; // units of microsecond
+ UInt32 unknown1;
+ UInt32 keycode;
+ UInt32 unknown2;
+} ATV_IR_EVENT;
+
static io_object_t _findAppleRemoteDevice(const char *devName);
AppleRemote::Listener::~Listener()
@@ -43,6 +58,8 @@ AppleRemote * AppleRemote::Get()
AppleRemote::~AppleRemote()
{
stopListening();
+ if (mUsingNewAtv)
+ delete mCallbackTimer;
}
bool AppleRemote::isListeningToRemote()
@@ -107,14 +124,72 @@ void AppleRemote::run()
RunEpilog();
}
+// Figure out if we're running on the Apple TV, and what version
+static float GetATVversion()
+{
+ SInt32 macVersion;
+ size_t len = 512;
+ char hw_model[512] = "unknown";
+
+
+ Gestalt(gestaltSystemVersion, &macVersion);
+
+ if ( macVersion > 0x1040 ) // Mac OS 10.5 or greater is
+ return 0.0; // definitely not an Apple TV
+
+ sysctlbyname("hw.model", &hw_model, &len, NULL, 0);
+
+ if ( strstr(hw_model,"AppleTV1,1") )
+ {
+ FILE *inpipe;
+ char linebuf[1000];
+ float version = 0.0;
+
+ // Find the build version of the AppleTV OS
+ inpipe = popen("sw_vers -buildVersion", "r");
+ if (inpipe && fgets(linebuf, sizeof(linebuf) - 1, inpipe) )
+ {
+ if ( strstr(linebuf,"8N5107") ) version = 1.0;
+ else if (strstr(linebuf,"8N5239") ) version = 1.1;
+ else if (strstr(linebuf,"8N5400") ) version = 2.0;
+ else if (strstr(linebuf,"8N5455") ) version = 2.01;
+ else if (strstr(linebuf,"8N5461") ) version = 2.02;
+ else if (strstr(linebuf,"8N5519") ) version = 2.1;
+ else if (strstr(linebuf,"8N5622") ) version = 2.2;
+ else
+ version = 2.3;
+
+ pclose(inpipe);
+
+ return version;
+ }
+ }
+}
+
// protected
AppleRemote::AppleRemote() : MThread("AppleRemote"),
openInExclusiveMode(true),
hidDeviceInterface(0),
queue(0),
remoteId(0),
- _listener(0)
+ _listener(0),
+ mUsingNewAtv(false),
+ mLastEvent(AppleRemote::Undefined),
+ mEventCount(0),
+ mKeyIsDown(false)
{
+ if ( GetATVversion() > 2.2 )
+ {
+ LOG(VB_GENERAL, LOG_INFO,
+ LOC + "AppleRemote() detected Apple TV > v2.3");
+ mUsingNewAtv = true;
+ mCallbackTimer = new QTimer();
+ QObject::connect(mCallbackTimer, SIGNAL(timeout()),
+ (const QObject*)this, SLOT(TimeoutHandler()));
+ mCallbackTimer->setSingleShot(true);
+ mCallbackTimer->setInterval(KEY_RESPONSE_TIME);
+ }
+
_initCookieMap();
}
@@ -369,7 +444,10 @@ void AppleRemote::QueueCallbackFunction(void* target, IOReturn result,
{
AppleRemote* remote = static_cast<AppleRemote*>(target);
- remote->_queueCallbackFunction(result,refcon,sender);
+ if (remote->mUsingNewAtv)
+ remote->_queueCallbackATV23(result);
+ else
+ remote->_queueCallbackFunction(result, refcon, sender);
}
void AppleRemote::_queueCallbackFunction(IOReturn result,
@@ -390,7 +468,7 @@ void AppleRemote::_queueCallbackFunction(IOReturn result,
if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie)
{
remoteId=event.value;
- _handleEventWithCookieString("19_",0);
+ _handleEventWithCookieString(REMOTE_COOKIE_STR, 0);
}
else
{
@@ -402,6 +480,49 @@ void AppleRemote::_queueCallbackFunction(IOReturn result,
_handleEventWithCookieString(cookieString.str(), sumOfValues);
}
+void AppleRemote::_queueCallbackATV23(IOReturn result)
+{
+ AbsoluteTime zeroTime = {0,0};
+ SInt32 sumOfValues = 0;
+ std::stringstream cookieString;
+ UInt32 key_code = 0;
+
+
+ if (mCallbackTimer->isActive())
+ {
+ mCallbackTimer->stop();
+ }
+
+ while (result == kIOReturnSuccess)
+ {
+ IOHIDEventStruct event;
+
+ result = (*queue)->getNextEvent(queue, &event, zeroTime, 0);
+ if (result != kIOReturnSuccess)
+ continue;
+
+ if ( ((int)event.elementCookie == 280) && (event.longValueSize == 20))
+ {
+ ATV_IR_EVENT* atv_ir_event = (ATV_IR_EVENT*)event.longValue;
+ key_code = atv_ir_event->keycode;
+ }
+
+ if (((int)event.elementCookie) != 5 )
+ {
+ sumOfValues += event.value;
+ cookieString << std::dec << (int)event.elementCookie << "_";
+ }
+ }
+
+ if (strcmp(cookieString.str().c_str(), ATV_COOKIE_STR) == 0)
+ {
+ cookieString << std::dec << (int) ( (key_code & 0x00007F00) >> 8);
+
+ sumOfValues = 1;
+ _handleEventATV23(cookieString.str(), sumOfValues);
+ }
+}
+
void AppleRemote::_handleEventWithCookieString(std::string cookieString,
SInt32 sumOfValues)
{
@@ -415,3 +536,138 @@ void AppleRemote::_handleEventWithCookieString(std::string cookieString,
_listener->appleRemoteButton(buttonid, sumOfValues>0);
}
}
+
+// With the ATV from 2.3 onwards, we just get IR events.
+// We need to simulate the key up and hold events
+
+void AppleRemote::_handleEventATV23(std::string cookieString,
+ SInt32 sumOfValues)
+{
+ std::map<std::string,AppleRemote::Event>::iterator ii;
+ ii = cookieToButtonMapping.find(cookieString);
+
+ if (ii != cookieToButtonMapping.end() )
+ {
+ AppleRemote::Event event = ii->second;
+
+ if (mLastEvent == Undefined) // new event
+ {
+ mEventCount = 1;
+ // Need to figure out if this is a long press or a short press,
+ // so can't just send a key down event right now. It will be
+ // scheduled to run
+ }
+ else if (event != mLastEvent) // a new event, faster than timer
+ {
+ mEventCount = 1;
+ mKeyIsDown = true;
+
+ if (_listener)
+ {
+ // Only send key up events for events that have separateRelease
+ // defined as true in AppleRemoteListener.cpp
+ if (mLastEvent == Up || mLastEvent == Down ||
+ mLastEvent == LeftHold || mLastEvent == RightHold)
+ {
+ _listener->appleRemoteButton(mLastEvent,
+ /*pressedDown*/false);
+ }
+ _listener->appleRemoteButton(event, mKeyIsDown);
+ }
+ }
+ else // Same event again
+ {
+ AppleRemote::Event newEvent = Undefined;
+
+ ++mEventCount;
+
+ // Can the event have a hold state?
+ switch (event)
+ {
+ case Right:
+ newEvent = RightHold;
+ break;
+ case Left:
+ newEvent = LeftHold;
+ break;
+ case Menu:
+ newEvent = MenuHold;
+ break;
+ case Select:
+ newEvent = PlayHold;
+ break;
+ default:
+ newEvent = event;
+ }
+
+ if (newEvent == event) // Doesn't have a long press
+ {
+ if (mKeyIsDown)
+ {
+ if (_listener)
+ {
+ // Only send key up events for events that have separateRelease
+ // defined as true in AppleRemoteListener.cpp
+ if (mLastEvent == Up || mLastEvent == Down ||
+ mLastEvent == LeftHold || mLastEvent == RightHold)
+ {
+ _listener->appleRemoteButton(mLastEvent, /*pressedDown*/false);
+ }
+ }
+ }
+
+ mKeyIsDown = true;
+ if (_listener)
+ {
+ _listener->appleRemoteButton(newEvent, mKeyIsDown);
+ }
+ }
+ else if (mEventCount == LONG_PRESS_COUNT)
+ {
+ mKeyIsDown = true;
+ if (_listener)
+ {
+ _listener->appleRemoteButton(newEvent, mKeyIsDown);
+ }
+ }
+ }
+
+ mLastEvent = event;
+ mCallbackTimer->start();
+ }
+}
+
+// Calls key down / up events on the ATV > v2.3
+void AppleRemote::TimeoutHandler()
+{
+ if (_listener)
+ {
+ _listener->appleRemoteButton(mLastEvent, !mKeyIsDown);
+ }
+
+ mKeyIsDown = !mKeyIsDown;
+
+ if (!mKeyIsDown)
+ {
+ mEventCount = 0;
+ mLastEvent = Undefined;
+ }
+ else
+ {
+ // Schedule a key up event for events that have separateRelease
+ // defined as true in AppleRemoteListener.cpp
+
+ if (mLastEvent == Up || mLastEvent == Down ||
+ mLastEvent == LeftHold || mLastEvent == RightHold)
+ {
+ mCallbackTimer->start();
+ }
+ else
+ {
+ mKeyIsDown = false;
+ mEventCount = 0;
+ mLastEvent = Undefined;
+ }
+
+ }
+}
@@ -9,14 +9,17 @@
// MythTV headers
#include "mthread.h"
+#include <QTimer>
+
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <CoreFoundation/CoreFoundation.h>
-class AppleRemote : public MThread
+class AppleRemote : public QObject, public MThread
{
+ Q_OBJECT
public:
enum Event
{ // label/meaning on White ... and Aluminium remote
@@ -31,7 +34,8 @@ class AppleRemote : public MThread
MenuHold,
PlayHold, // was PlaySleep
ControlSwitched,
- PlayPause // Play or Pause
+ PlayPause, // Play or Pause
+ Undefined // Used to handle the Apple TV > v2.3
};
class Listener
@@ -68,6 +72,12 @@ class AppleRemote : public MThread
int remoteId;
Listener* _listener;
+ bool mUsingNewAtv;
+ AppleRemote::Event mLastEvent;
+ int mEventCount;
+ bool mKeyIsDown;
+ QTimer* mCallbackTimer;
+
void _initCookieMap();
bool _initCookies();
bool _createDeviceInterface(io_object_t hidDevice);
@@ -77,8 +87,14 @@ class AppleRemote : public MThread
void* refcon, void* sender);
void _queueCallbackFunction(IOReturn result,
void* refcon, void* sender);
+ void _queueCallbackATV23(IOReturn result);
void _handleEventWithCookieString(std::string cookieString,
SInt32 sumOfValues);
+ void _handleEventATV23(std::string cookieString, SInt32 sumOfValues);
+
+private slots:
+ // Key up event handling on the ATV v2.3 and above
+ void TimeoutHandler();
};
#endif // APPLEREMOTE

0 comments on commit 596f98b

Please sign in to comment.