From 596f98b086aa457d90d4ce376be7c059ba9680a5 Mon Sep 17 00:00:00 2001 From: Nigel Pearson Date: Fri, 21 Oct 2011 07:56:11 +1100 Subject: [PATCH] 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. --- mythtv/libs/libmythui/AppleRemote.cpp | 262 +++++++++++++++++++++++++- mythtv/libs/libmythui/AppleRemote.h | 20 +- 2 files changed, 277 insertions(+), 5 deletions(-) diff --git a/mythtv/libs/libmythui/AppleRemote.cpp b/mythtv/libs/libmythui/AppleRemote.cpp index a13296bde06..c0a96e25c6b 100644 --- a/mythtv/libs/libmythui/AppleRemote.cpp +++ b/mythtv/libs/libmythui/AppleRemote.cpp @@ -6,6 +6,7 @@ #include #include #include +#include // for sysctlbyname #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include // for Gestalt #include @@ -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(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::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; + } + + } +} diff --git a/mythtv/libs/libmythui/AppleRemote.h b/mythtv/libs/libmythui/AppleRemote.h index c05e5513569..f1d0d60e08f 100644 --- a/mythtv/libs/libmythui/AppleRemote.h +++ b/mythtv/libs/libmythui/AppleRemote.h @@ -9,14 +9,17 @@ // MythTV headers #include "mthread.h" +#include + #include #include #include #include #include -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