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