Skip to content

Commit

Permalink
AppleTV 2.3 remote support, thanks to Emlyn Bolton. Refs #6422.
Browse files Browse the repository at this point in the history
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
NigelPearson committed Oct 20, 2011
1 parent 6bf25f0 commit 596f98b
Show file tree
Hide file tree
Showing 2 changed files with 277 additions and 5 deletions.
262 changes: 259 additions & 3 deletions mythtv/libs/libmythui/AppleRemote.cpp
Expand Up @@ -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>
Expand All @@ -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>

Expand All @@ -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()
Expand All @@ -43,6 +58,8 @@ AppleRemote * AppleRemote::Get()
AppleRemote::~AppleRemote()
{
stopListening();
if (mUsingNewAtv)
delete mCallbackTimer;
}

bool AppleRemote::isListeningToRemote()
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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,
Expand All @@ -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
{
Expand All @@ -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)
{
Expand All @@ -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;
}

}
}
20 changes: 18 additions & 2 deletions mythtv/libs/libmythui/AppleRemote.h
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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.