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