MythTV master
AppleRemote.cpp
Go to the documentation of this file.
1
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
24#include "libmythbase/mythconfig.h"
26
27#if !HAVE_IOMAINPORT
28#define kIOMainPortDefault kIOMasterPortDefault
29#endif
30
32
33
34static constexpr int REMOTE_SWITCH_COOKIE { 19 };
35static constexpr const char* REMOTE_COOKIE_STR { "19_" };
36
37#define LOC QString("AppleRemote::")
38
39static io_object_t findAppleRemoteDevice(const char *devName);
40
42{
43 if (_instance == nullptr)
44 _instance = new AppleRemote();
45
46 return _instance;
47}
48
50{
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) ||
86 {
87 LOG(VB_GENERAL, LOG_ERR, LOC + "startListening() failed");
89 return;
90 }
91
92 IOObjectRelease(hidDevice);
93}
94
95// NOLINTBEGIN(bugprone-multi-level-implicit-pointer-conversion)
97{
98 if (queue != nullptr)
99 {
100 (*queue)->stop(queue);
101 (*queue)->dispose(queue);
102 (*queue)->Release(queue);
103 queue = nullptr;
104 }
105
106 if (!cookies.empty())
107 cookies.clear();
108
109 if (hidDeviceInterface != nullptr)
110 {
111 (*hidDeviceInterface)->close(hidDeviceInterface);
112 (*hidDeviceInterface)->Release(hidDeviceInterface);
113 hidDeviceInterface = nullptr;
114 }
115}
116// NOLINTEND(bugprone-multi-level-implicit-pointer-conversion)
117
119{
120 RunProlog();
121 CFRunLoopRun();
122 exec(); // prevent QThread exiting, by entering its run loop
123 CFRunLoopStop(CFRunLoopGetCurrent());
124 RunEpilog();
125}
126
128{
130}
131
142{
143 // 10.4 sequences:
144 cookieToButtonMapping["14_12_11_6_5_"] = Up;
145 cookieToButtonMapping["14_13_11_6_5_"] = Down;
146 cookieToButtonMapping["14_7_6_5_14_7_6_5_"] = Menu;
147 cookieToButtonMapping["14_8_6_5_14_8_6_5_"] = Select;
148 cookieToButtonMapping["14_9_6_5_14_9_6_5_"] = Right;
149 cookieToButtonMapping["14_10_6_5_14_10_6_5_"] = Left;
150 cookieToButtonMapping["14_6_5_4_2_"] = RightHold;
151 cookieToButtonMapping["14_6_5_3_2_"] = LeftHold;
152 cookieToButtonMapping["14_6_5_14_6_5_"] = MenuHold;
153 cookieToButtonMapping["18_14_6_5_18_14_6_5_"] = PlayHold;
155
156 // 10.5 sequences:
157 cookieToButtonMapping["31_29_28_18_"] = Up;
158 cookieToButtonMapping["31_30_28_18_"] = Down;
159 cookieToButtonMapping["31_20_18_31_20_18_"] = Menu;
160 cookieToButtonMapping["31_21_18_31_21_18_"] = Select;
161 cookieToButtonMapping["31_22_18_31_22_18_"] = Right;
162 cookieToButtonMapping["31_23_18_31_23_18_"] = Left;
163 cookieToButtonMapping["31_18_4_2_"] = RightHold;
164 cookieToButtonMapping["31_18_3_2_"] = LeftHold;
165 cookieToButtonMapping["31_18_31_18_"] = MenuHold;
166 cookieToButtonMapping["35_31_18_35_31_18_"] = PlayHold;
168
169 // 10.6 sequences:
170 cookieToButtonMapping["33_31_30_21_20_2_"] = Up;
171 cookieToButtonMapping["33_32_30_21_20_2_"] = Down;
172 cookieToButtonMapping["33_22_21_20_2_33_22_21_20_2_"] = Menu;
173 cookieToButtonMapping["33_23_21_20_2_33_23_21_20_2_"] = Select;
174 cookieToButtonMapping["33_24_21_20_2_33_24_21_20_2_"] = Right;
175 cookieToButtonMapping["33_25_21_20_2_33_25_21_20_2_"] = Left;
176 cookieToButtonMapping["33_21_20_14_12_2_"] = RightHold;
177 cookieToButtonMapping["33_21_20_13_12_2_"] = LeftHold;
178 cookieToButtonMapping["33_21_20_2_33_21_20_2_"] = MenuHold;
179 cookieToButtonMapping["37_33_21_20_2_37_33_21_20_2_"] = PlayHold;
180
181 // Aluminium remote which has an extra button:
182 cookieToButtonMapping["33_21_20_8_2_33_21_20_8_2_"] = PlayPause;
183 cookieToButtonMapping["33_21_20_3_2_33_21_20_3_2_"] = Select;
184 cookieToButtonMapping["33_21_20_11_2_33_21_20_11_2_"] = PlayHold;
185}
186
187static io_object_t findAppleRemoteDevice(const char *devName)
188{
189 CFMutableDictionaryRef hidMatchDictionary = nullptr;
190 io_iterator_t hidObjectIterator = 0;
191 io_object_t hidDevice = 0;
192 IOReturn ioReturnValue = 0;
193
194
195 hidMatchDictionary = IOServiceMatching(devName);
196
197 // check for matching devices
198 ioReturnValue = IOServiceGetMatchingServices(kIOMainPortDefault,
199 hidMatchDictionary,
200 &hidObjectIterator);
201
202 if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0))
203 hidDevice = IOIteratorNext(hidObjectIterator);
204 else
205 LOG(VB_GENERAL, LOG_ERR, LOC +
206 QString("findAppleRemoteDevice(%1) failed").arg(devName));
207
208 // IOServiceGetMatchingServices consumes a reference to the dictionary,
209 // so we don't need to release the dictionary ref.
210 hidMatchDictionary = nullptr;
211 return hidDevice;
212}
213
215{
216 IOHIDDeviceInterface122** handle = nullptr;
217 CFArrayRef elements = nullptr;
218 IOReturn success = 0;
219
220 handle = (IOHIDDeviceInterface122**)hidDeviceInterface;
221 success = (*handle)->copyMatchingElements((void*)handle, nullptr, &elements);
222
223 if (success == kIOReturnSuccess)
224 {
225 for (CFIndex i = 0; i < CFArrayGetCount(elements); ++i)
226 {
227 CFDictionaryRef element = nullptr;
228 CFTypeRef object = nullptr;
229 long number = 0;
230 IOHIDElementCookie cookie = 0;
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
252bool AppleRemote::_createDeviceInterface(io_object_t hidDevice)
253{
254 IOReturn ioReturnValue = 0;
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 ((void*)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((void*)plugInInterface);
277 }
278 return hidDeviceInterface != nullptr;
279}
280
281// NOLINTBEGIN(bugprone-multi-level-implicit-pointer-conversion)
283{
284 CFRunLoopSourceRef eventSource = nullptr;
285 IOReturn ioReturnValue = 0;
286 IOHIDOptionsType openMode = 0;
287
288
290 openMode = kIOHIDOptionsTypeSeizeDevice;
291 else
292 openMode = kIOHIDOptionsTypeNone;
293
294 ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode);
295
296 if (ioReturnValue != KERN_SUCCESS)
297 {
298 LOG(VB_GENERAL, LOG_ERR, LOC + "_openDevice() failed");
299 return false;
300 }
301 queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface);
302 if (!queue)
303 {
304 LOG(VB_GENERAL, LOG_ERR, LOC +
305 "_openDevice() - error allocating queue");
306 return false;
307 }
308
309 HRESULT result = (*queue)->create(queue, 0, 12);
310 if (result != S_OK || !queue)
311 LOG(VB_GENERAL, LOG_ERR, LOC + "_openDevice() - error creating queue");
312
313 for (int & iter : cookies)
314 {
315 auto cookie = (IOHIDElementCookie)iter;
316 (*queue)->addElement(queue, cookie, 0);
317 }
318
319 ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource);
320 if (ioReturnValue != KERN_SUCCESS)
321 {
322 LOG(VB_GENERAL, LOG_ERR, LOC +
323 "_openDevice() - failed to create async event source");
324 return false;
325 }
326
327 ioReturnValue = (*queue)->setEventCallout(queue, QueueCallbackFunction,
328 this, nullptr);
329 if (ioReturnValue != KERN_SUCCESS)
330 {
331 LOG(VB_GENERAL, LOG_ERR, LOC +
332 "_openDevice() - error registering callback");
333 return false;
334 }
335
336 CFRunLoopAddSource(CFRunLoopGetCurrent(),
337 eventSource, kCFRunLoopDefaultMode);
338 (*queue)->start(queue);
339 return true;
340}
341// NOLINTEND(bugprone-multi-level-implicit-pointer-conversion)
342
343void AppleRemote::QueueCallbackFunction(void* target, IOReturn result,
344 void* refcon, void* sender)
345{
346 auto* remote = static_cast<AppleRemote*>(target);
347
348 remote->_queueCallbackFunction(result, refcon, sender);
349}
350
352 void* /*refcon*/, void* /*sender*/)
353{
354 AbsoluteTime zeroTime = {0,0};
355 SInt32 sumOfValues = 0;
356 std::stringstream cookieString;
357
358 while (result == kIOReturnSuccess)
359 {
360 IOHIDEventStruct event;
361
362 result = (*queue)->getNextEvent((void*)queue, &event, zeroTime, 0);
363 if (result != kIOReturnSuccess)
364 break;
365
366 if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie)
367 {
368 remoteId=event.value;
370 }
371 else
372 {
373 sumOfValues+=event.value;
374 cookieString << std::dec << (int)event.elementCookie << "_";
375 }
376 }
377
378 _handleEventWithCookieString(cookieString.str(), sumOfValues);
379}
380
381void AppleRemote::_handleEventWithCookieString(const std::string& cookieString,
382 SInt32 sumOfValues)
383{
384 std::map<std::string,AppleRemote::Event>::iterator ii;
385
386 ii = cookieToButtonMapping.find(cookieString);
387 if (ii != cookieToButtonMapping.end() && _listener)
388 {
389 AppleRemote::Event buttonid = ii->second;
390 if (_listener)
391 _listener->appleRemoteButton(buttonid, sumOfValues>0);
392 }
393}
#define LOC
Definition: AppleRemote.cpp:37
static constexpr int REMOTE_SWITCH_COOKIE
Definition: AppleRemote.cpp:34
static constexpr const char * REMOTE_COOKIE_STR
Definition: AppleRemote.cpp:35
static io_object_t findAppleRemoteDevice(const char *devName)
#define kIOMainPortDefault
Definition: AppleRemote.cpp:28
virtual void appleRemoteButton(Event button, bool pressedDown)=0
std::vector< int > cookies
Definition: AppleRemote.h:70
IOHIDDeviceInterface ** hidDeviceInterface
Definition: AppleRemote.h:68
void _initCookieMap()
Apple keeps changing the "interface" between the remote and the OS.
bool openInExclusiveMode
Definition: AppleRemote.h:67
std::map< std::string, Event > cookieToButtonMapping
Definition: AppleRemote.h:71
static AppleRemote * _instance
Definition: AppleRemote.h:63
void setListener(Listener *listener)
Definition: AppleRemote.cpp:68
bool _initCookies()
bool _createDeviceInterface(io_object_t hidDevice)
Listener * _listener
Definition: AppleRemote.h:73
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
bool _openDevice()
static AppleRemote * Get()
Definition: AppleRemote.cpp:41
Listener * listener()
Definition: AppleRemote.h:53
IOHIDQueueInterface ** queue
Definition: AppleRemote.h:69
void _queueCallbackFunction(IOReturn result, void *refcon, void *sender)
bool isListeningToRemote()
Definition: AppleRemote.cpp:63
void stopListening()
Definition: AppleRemote.cpp:96
static void QueueCallbackFunction(void *target, IOReturn result, void *refcon, void *sender)
void startListening()
Definition: AppleRemote.cpp:73
void _handleEventWithCookieString(const std::string &cookieString, SInt32 sumOfValues)
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
bool isRunning(void) const
Definition: mthread.cpp:247
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:180
int exec(void)
Enters the qt event loop. call exit or quit to exit thread.
Definition: mthread.cpp:309
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:193
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
Definition: mthread.cpp:262
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39