MythTV  master
AppleRemote.cpp
Go to the documentation of this file.
1 
2 #include "devices/AppleRemote.h"
3 
4 #include <cctype>
5 #include <cstdio>
6 #include <cstdlib>
7 #include <sys/errno.h>
8 #include <sys/sysctl.h> // for sysctlbyname
9 #include <sysexits.h>
10 #include <unistd.h>
11 
12 #include <mach/mach.h>
13 #include <mach/mach_error.h>
14 #include <IOKit/IOKitLib.h>
15 #include <IOKit/IOCFPlugIn.h>
16 #include <IOKit/hid/IOHIDLib.h>
17 #include <IOKit/hid/IOHIDKeys.h>
18 #include <CoreFoundation/CoreFoundation.h>
19 #include <CoreServices/CoreServices.h> // for Gestalt
20 
21 #include <sstream>
22 
23 #include "mythlogging.h"
24 
26 
27 
28 #define REMOTE_SWITCH_COOKIE 19
29 #define REMOTE_COOKIE_STR "19_"
30 #define LONG_PRESS_COUNT 10
31 #define KEY_RESPONSE_TIME 150 /* msecs before we send a key up event */
32 
33 #define LOC QString("AppleRemote::")
34 
35 static io_object_t _findAppleRemoteDevice(const char *devName);
36 
38 {
39  if (_instance == nullptr)
40  _instance = new AppleRemote();
41 
42  return _instance;
43 }
44 
46 {
47  stopListening();
48 
49  if (isRunning())
50  {
51  exit(0);
52  }
53  if (this == _instance)
54  {
55  _instance = nullptr;
56  }
57 }
58 
60 {
61  return (hidDeviceInterface != nullptr && !cookies.empty() && queue != nullptr);
62 }
63 
65 {
67 }
68 
70 {
71  if (queue != nullptr) // already listening
72  return;
73 
74  io_object_t hidDevice = _findAppleRemoteDevice("AppleIRController");
75 
76  if (!hidDevice)
77  hidDevice = _findAppleRemoteDevice("AppleTVIRReceiver");
78 
79  if (!hidDevice ||
80  !_createDeviceInterface(hidDevice) ||
81  !_initCookies() || !_openDevice())
82  {
83  LOG(VB_GENERAL, LOG_ERR, LOC + "startListening() failed");
84  stopListening();
85  return;
86  }
87 
88  IOObjectRelease(hidDevice);
89 }
90 
92 {
93  if (queue != nullptr)
94  {
95  (*queue)->stop(queue);
96  (*queue)->dispose(queue);
97  (*queue)->Release(queue);
98  queue = nullptr;
99  }
100 
101  if (!cookies.empty())
102  cookies.clear();
103 
104  if (hidDeviceInterface != nullptr)
105  {
106  (*hidDeviceInterface)->close(hidDeviceInterface);
107  (*hidDeviceInterface)->Release(hidDeviceInterface);
108  hidDeviceInterface = nullptr;
109  }
110 }
111 
113 {
114  RunProlog();
115  CFRunLoopRun();
116  exec(); // prevent QThread exiting, by entering its run loop
117  CFRunLoopStop(CFRunLoopGetCurrent());
118  RunEpilog();
119 }
120 
122 {
123  _initCookieMap();
124 }
125 
136 {
137  // 10.4 sequences:
138  cookieToButtonMapping["14_12_11_6_5_"] = Up;
139  cookieToButtonMapping["14_13_11_6_5_"] = Down;
140  cookieToButtonMapping["14_7_6_5_14_7_6_5_"] = Menu;
141  cookieToButtonMapping["14_8_6_5_14_8_6_5_"] = Select;
142  cookieToButtonMapping["14_9_6_5_14_9_6_5_"] = Right;
143  cookieToButtonMapping["14_10_6_5_14_10_6_5_"] = Left;
144  cookieToButtonMapping["14_6_5_4_2_"] = RightHold;
145  cookieToButtonMapping["14_6_5_3_2_"] = LeftHold;
146  cookieToButtonMapping["14_6_5_14_6_5_"] = MenuHold;
147  cookieToButtonMapping["18_14_6_5_18_14_6_5_"] = PlayHold;
149 
150  // 10.5 sequences:
151  cookieToButtonMapping["31_29_28_18_"] = Up;
152  cookieToButtonMapping["31_30_28_18_"] = Down;
153  cookieToButtonMapping["31_20_18_31_20_18_"] = Menu;
154  cookieToButtonMapping["31_21_18_31_21_18_"] = Select;
155  cookieToButtonMapping["31_22_18_31_22_18_"] = Right;
156  cookieToButtonMapping["31_23_18_31_23_18_"] = Left;
157  cookieToButtonMapping["31_18_4_2_"] = RightHold;
158  cookieToButtonMapping["31_18_3_2_"] = LeftHold;
159  cookieToButtonMapping["31_18_31_18_"] = MenuHold;
160  cookieToButtonMapping["35_31_18_35_31_18_"] = PlayHold;
162 
163  // 10.6 sequences:
164  cookieToButtonMapping["33_31_30_21_20_2_"] = Up;
165  cookieToButtonMapping["33_32_30_21_20_2_"] = Down;
166  cookieToButtonMapping["33_22_21_20_2_33_22_21_20_2_"] = Menu;
167  cookieToButtonMapping["33_23_21_20_2_33_23_21_20_2_"] = Select;
168  cookieToButtonMapping["33_24_21_20_2_33_24_21_20_2_"] = Right;
169  cookieToButtonMapping["33_25_21_20_2_33_25_21_20_2_"] = Left;
170  cookieToButtonMapping["33_21_20_14_12_2_"] = RightHold;
171  cookieToButtonMapping["33_21_20_13_12_2_"] = LeftHold;
172  cookieToButtonMapping["33_21_20_2_33_21_20_2_"] = MenuHold;
173  cookieToButtonMapping["37_33_21_20_2_37_33_21_20_2_"] = PlayHold;
174 
175  // Aluminium remote which has an extra button:
176  cookieToButtonMapping["33_21_20_8_2_33_21_20_8_2_"] = PlayPause;
177  cookieToButtonMapping["33_21_20_3_2_33_21_20_3_2_"] = Select;
178  cookieToButtonMapping["33_21_20_11_2_33_21_20_11_2_"] = PlayHold;
179 }
180 
181 static io_object_t _findAppleRemoteDevice(const char *devName)
182 {
183  CFMutableDictionaryRef hidMatchDictionary = nullptr;
184  io_iterator_t hidObjectIterator = 0;
185  io_object_t hidDevice = 0;
186  IOReturn ioReturnValue;
187 
188 
189  hidMatchDictionary = IOServiceMatching(devName);
190 
191  // check for matching devices
192  ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault,
193  hidMatchDictionary,
194  &hidObjectIterator);
195 
196  if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0))
197  hidDevice = IOIteratorNext(hidObjectIterator);
198  else
199  LOG(VB_GENERAL, LOG_ERR, LOC +
200  QString("_findAppleRemoteDevice(%1) failed").arg(devName));
201 
202  // IOServiceGetMatchingServices consumes a reference to the dictionary,
203  // so we don't need to release the dictionary ref.
204  hidMatchDictionary = nullptr;
205  return hidDevice;
206 }
207 
209 {
210  IOHIDDeviceInterface122** handle;
211  CFArrayRef elements;
212  IOReturn success;
213 
214  handle = (IOHIDDeviceInterface122**)hidDeviceInterface;
215  success = (*handle)->copyMatchingElements(handle,
216  nullptr,
217  (CFArrayRef*)&elements);
218 
219  if (success == kIOReturnSuccess)
220  {
221  for (CFIndex i = 0; i < CFArrayGetCount(elements); ++i)
222  {
223  CFDictionaryRef element;
224  CFTypeRef object;
225  long number;
226  IOHIDElementCookie cookie;
227 
228  element = (CFDictionaryRef)CFArrayGetValueAtIndex(elements, i);
229  object = CFDictionaryGetValue(element,
230  CFSTR(kIOHIDElementCookieKey));
231 
232  if (object == nullptr || CFGetTypeID(object) != CFNumberGetTypeID())
233  continue;
234 
235  if (!CFNumberGetValue((CFNumberRef)object,
236  kCFNumberLongType, &number))
237  continue;
238 
239  cookie = (IOHIDElementCookie)number;
240 
241  cookies.push_back((int)cookie);
242  }
243  return true;
244  }
245  return false;
246 }
247 
248 bool AppleRemote::_createDeviceInterface(io_object_t hidDevice)
249 {
250  IOReturn ioReturnValue;
251  IOCFPlugInInterface** plugInInterface = nullptr;
252  SInt32 score = 0;
253 
254 
255  ioReturnValue
256  = IOCreatePlugInInterfaceForService(hidDevice,
257  kIOHIDDeviceUserClientTypeID,
258  kIOCFPlugInInterfaceID,
259  &plugInInterface, &score);
260 
261  if ((kIOReturnSuccess == ioReturnValue) &&
262  plugInInterface && *plugInInterface)
263  {
264  HRESULT plugInResult = (*plugInInterface)->QueryInterface
265  (plugInInterface,
266  CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID),
267  (LPVOID*) (&hidDeviceInterface));
268 
269  if (plugInResult != S_OK)
270  LOG(VB_GENERAL, LOG_ERR, LOC + "_createDeviceInterface() failed");
271 
272  (*plugInInterface)->Release(plugInInterface);
273  }
274  return hidDeviceInterface != nullptr;
275 }
276 
278 {
279  CFRunLoopSourceRef eventSource;
280  IOReturn ioReturnValue;
281  IOHIDOptionsType openMode;
282 
283 
285  openMode = kIOHIDOptionsTypeSeizeDevice;
286  else
287  openMode = kIOHIDOptionsTypeNone;
288 
289  ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode);
290 
291  if (ioReturnValue != KERN_SUCCESS)
292  {
293  LOG(VB_GENERAL, LOG_ERR, LOC + "_openDevice() failed");
294  return false;
295  }
296  queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface);
297  if (!queue)
298  {
299  LOG(VB_GENERAL, LOG_ERR, LOC +
300  "_openDevice() - error allocating queue");
301  return false;
302  }
303 
304  HRESULT result = (*queue)->create(queue, 0, 12);
305  if (result != S_OK || !queue)
306  LOG(VB_GENERAL, LOG_ERR, LOC + "_openDevice() - error creating queue");
307 
308  for (int & iter : cookies)
309  {
310  auto cookie = (IOHIDElementCookie)iter;
311  (*queue)->addElement(queue, cookie, 0);
312  }
313 
314  ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource);
315  if (ioReturnValue != KERN_SUCCESS)
316  {
317  LOG(VB_GENERAL, LOG_ERR, LOC +
318  "_openDevice() - failed to create async event source");
319  return false;
320  }
321 
322  ioReturnValue = (*queue)->setEventCallout(queue, QueueCallbackFunction,
323  this, nullptr);
324  if (ioReturnValue != KERN_SUCCESS)
325  {
326  LOG(VB_GENERAL, LOG_ERR, LOC +
327  "_openDevice() - error registering callback");
328  return false;
329  }
330 
331  CFRunLoopAddSource(CFRunLoopGetCurrent(),
332  eventSource, kCFRunLoopDefaultMode);
333  (*queue)->start(queue);
334  return true;
335 }
336 
337 void AppleRemote::QueueCallbackFunction(void* target, IOReturn result,
338  void* refcon, void* sender)
339 {
340  auto* remote = static_cast<AppleRemote*>(target);
341 
342  remote->_queueCallbackFunction(result, refcon, sender);
343 }
344 
346  void* /*refcon*/, void* /*sender*/)
347 {
348  AbsoluteTime zeroTime = {0,0};
349  SInt32 sumOfValues = 0;
350  std::stringstream cookieString;
351 
352  while (result == kIOReturnSuccess)
353  {
354  IOHIDEventStruct event;
355 
356  result = (*queue)->getNextEvent(queue, &event, zeroTime, 0);
357  if (result != kIOReturnSuccess)
358  break;
359 
360  if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie)
361  {
362  remoteId=event.value;
364  }
365  else
366  {
367  sumOfValues+=event.value;
368  cookieString << std::dec << (int)event.elementCookie << "_";
369  }
370  }
371 
372  _handleEventWithCookieString(cookieString.str(), sumOfValues);
373 }
374 
375 void AppleRemote::_handleEventWithCookieString(std::string cookieString,
376  SInt32 sumOfValues)
377 {
378  std::map<std::string,AppleRemote::Event>::iterator ii;
379 
380  ii = cookieToButtonMapping.find(cookieString);
381  if (ii != cookieToButtonMapping.end() && _listener)
382  {
383  AppleRemote::Event buttonid = ii->second;
384  if (_listener)
385  _listener->appleRemoteButton(buttonid, sumOfValues>0);
386  }
387 }
AppleRemote::Get
static AppleRemote * Get()
Definition: AppleRemote.cpp:37
MThread::exec
int exec(void)
Enters the qt event loop. call exit or quit to exit thread.
Definition: mthread.cpp:326
REMOTE_SWITCH_COOKIE
#define REMOTE_SWITCH_COOKIE
Definition: AppleRemote.cpp:28
AppleRemote::run
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: AppleRemote.cpp:112
AppleRemote::Select
@ Select
Definition: AppleRemote.h:29
AppleRemote::RightHold
@ RightHold
Definition: AppleRemote.h:32
arg
arg(title).arg(filename).arg(doDelete))
AppleRemote::Down
@ Down
Definition: AppleRemote.h:27
AppleRemote::_initCookieMap
void _initCookieMap()
Apple keeps changing the "interface" between the remote and the OS.
Definition: AppleRemote.cpp:135
AppleRemote::Event
Event
Definition: AppleRemote.h:25
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MThread::RunProlog
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:202
AppleRemote::MenuHold
@ MenuHold
Definition: AppleRemote.h:34
AppleRemote::_handleEventWithCookieString
void _handleEventWithCookieString(std::string cookieString, SInt32 sumOfValues)
Definition: AppleRemote.cpp:375
AppleRemote::Menu
@ Menu
Definition: AppleRemote.h:28
AppleRemote::QueueCallbackFunction
static void QueueCallbackFunction(void *target, IOReturn result, void *refcon, void *sender)
Definition: AppleRemote.cpp:337
AppleRemote::_openDevice
bool _openDevice()
Definition: AppleRemote.cpp:277
AppleRemote::Listener::appleRemoteButton
virtual void appleRemoteButton(Event button, bool pressedDown)=0
_findAppleRemoteDevice
static io_object_t _findAppleRemoteDevice(const char *devName)
Definition: AppleRemote.cpp:181
AppleRemote::Listener
Definition: AppleRemote.h:42
AppleRemote::AppleRemote
AppleRemote()
Definition: AppleRemote.cpp:121
AppleRemote::remoteId
int remoteId
Definition: AppleRemote.h:72
AppleRemote::Right
@ Right
Definition: AppleRemote.h:30
mythlogging.h
REMOTE_COOKIE_STR
#define REMOTE_COOKIE_STR
Definition: AppleRemote.cpp:29
AppleRemote
Definition: AppleRemote.h:21
AppleRemote::_queueCallbackFunction
void _queueCallbackFunction(IOReturn result, void *refcon, void *sender)
Definition: AppleRemote.cpp:345
AppleRemote::listener
Listener * listener()
Definition: AppleRemote.h:53
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:215
AppleRemote::openInExclusiveMode
bool openInExclusiveMode
Definition: AppleRemote.h:67
AppleRemote::PlayHold
@ PlayHold
Definition: AppleRemote.h:35
AppleRemote::~AppleRemote
~AppleRemote()
Definition: AppleRemote.cpp:45
AppleRemote::cookies
std::vector< int > cookies
Definition: AppleRemote.h:70
AppleRemote::hidDeviceInterface
IOHIDDeviceInterface ** hidDeviceInterface
Definition: AppleRemote.h:68
AppleRemote::ControlSwitched
@ ControlSwitched
Definition: AppleRemote.h:36
AppleRemote::_createDeviceInterface
bool _createDeviceInterface(io_object_t hidDevice)
Definition: AppleRemote.cpp:248
AppleRemote::startListening
void startListening()
Definition: AppleRemote.cpp:69
AppleRemote::cookieToButtonMapping
std::map< std::string, Event > cookieToButtonMapping
Definition: AppleRemote.h:71
AppleRemote::Up
@ Up
Definition: AppleRemote.h:26
AppleRemote::Left
@ Left
Definition: AppleRemote.h:31
AppleRemote::LeftHold
@ LeftHold
Definition: AppleRemote.h:33
AppleRemote::_instance
static AppleRemote * _instance
Definition: AppleRemote.h:63
MThread
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
AppleRemote::_initCookies
bool _initCookies()
Definition: AppleRemote.cpp:208
MThread::exit
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
Definition: mthread.cpp:287
MThread::isRunning
bool isRunning(void) const
Definition: mthread.cpp:272
AppleRemote::stopListening
void stopListening()
Definition: AppleRemote.cpp:91
AppleRemote::queue
IOHIDQueueInterface ** queue
Definition: AppleRemote.h:69
LOC
#define LOC
Definition: AppleRemote.cpp:33
AppleRemote::isListeningToRemote
bool isListeningToRemote()
Definition: AppleRemote.cpp:59
AppleRemote::_listener
Listener * _listener
Definition: AppleRemote.h:73
AppleRemote::setListener
void setListener(Listener *listener)
Definition: AppleRemote.cpp:64
AppleRemote.h
AppleRemote::PlayPause
@ PlayPause
Definition: AppleRemote.h:37