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
34#define REMOTE_SWITCH_COOKIE 19
35#define REMOTE_COOKIE_STR "19_"
36#define LONG_PRESS_COUNT 10
37
38#define LOC QString("AppleRemote::")
39
40static io_object_t _findAppleRemoteDevice(const char *devName);
41
43{
44 if (_instance == nullptr)
45 _instance = new AppleRemote();
46
47 return _instance;
48}
49
51{
53
54 if (isRunning())
55 {
56 exit(0);
57 }
58 if (this == _instance)
59 {
60 _instance = nullptr;
61 }
62}
63
65{
66 return (hidDeviceInterface != nullptr && !cookies.empty() && queue != nullptr);
67}
68
70{
72}
73
75{
76 if (queue != nullptr) // already listening
77 return;
78
79 io_object_t hidDevice = _findAppleRemoteDevice("AppleIRController");
80
81 if (!hidDevice)
82 hidDevice = _findAppleRemoteDevice("AppleTVIRReceiver");
83
84 if (!hidDevice ||
85 !_createDeviceInterface(hidDevice) ||
87 {
88 LOG(VB_GENERAL, LOG_ERR, LOC + "startListening() failed");
90 return;
91 }
92
93 IOObjectRelease(hidDevice);
94}
95
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
118{
119 RunProlog();
120 CFRunLoopRun();
121 exec(); // prevent QThread exiting, by entering its run loop
122 CFRunLoopStop(CFRunLoopGetCurrent());
123 RunEpilog();
124}
125
127{
129}
130
141{
142 // 10.4 sequences:
143 cookieToButtonMapping["14_12_11_6_5_"] = Up;
144 cookieToButtonMapping["14_13_11_6_5_"] = Down;
145 cookieToButtonMapping["14_7_6_5_14_7_6_5_"] = Menu;
146 cookieToButtonMapping["14_8_6_5_14_8_6_5_"] = Select;
147 cookieToButtonMapping["14_9_6_5_14_9_6_5_"] = Right;
148 cookieToButtonMapping["14_10_6_5_14_10_6_5_"] = Left;
149 cookieToButtonMapping["14_6_5_4_2_"] = RightHold;
150 cookieToButtonMapping["14_6_5_3_2_"] = LeftHold;
151 cookieToButtonMapping["14_6_5_14_6_5_"] = MenuHold;
152 cookieToButtonMapping["18_14_6_5_18_14_6_5_"] = PlayHold;
154
155 // 10.5 sequences:
156 cookieToButtonMapping["31_29_28_18_"] = Up;
157 cookieToButtonMapping["31_30_28_18_"] = Down;
158 cookieToButtonMapping["31_20_18_31_20_18_"] = Menu;
159 cookieToButtonMapping["31_21_18_31_21_18_"] = Select;
160 cookieToButtonMapping["31_22_18_31_22_18_"] = Right;
161 cookieToButtonMapping["31_23_18_31_23_18_"] = Left;
162 cookieToButtonMapping["31_18_4_2_"] = RightHold;
163 cookieToButtonMapping["31_18_3_2_"] = LeftHold;
164 cookieToButtonMapping["31_18_31_18_"] = MenuHold;
165 cookieToButtonMapping["35_31_18_35_31_18_"] = PlayHold;
167
168 // 10.6 sequences:
169 cookieToButtonMapping["33_31_30_21_20_2_"] = Up;
170 cookieToButtonMapping["33_32_30_21_20_2_"] = Down;
171 cookieToButtonMapping["33_22_21_20_2_33_22_21_20_2_"] = Menu;
172 cookieToButtonMapping["33_23_21_20_2_33_23_21_20_2_"] = Select;
173 cookieToButtonMapping["33_24_21_20_2_33_24_21_20_2_"] = Right;
174 cookieToButtonMapping["33_25_21_20_2_33_25_21_20_2_"] = Left;
175 cookieToButtonMapping["33_21_20_14_12_2_"] = RightHold;
176 cookieToButtonMapping["33_21_20_13_12_2_"] = LeftHold;
177 cookieToButtonMapping["33_21_20_2_33_21_20_2_"] = MenuHold;
178 cookieToButtonMapping["37_33_21_20_2_37_33_21_20_2_"] = PlayHold;
179
180 // Aluminium remote which has an extra button:
181 cookieToButtonMapping["33_21_20_8_2_33_21_20_8_2_"] = PlayPause;
182 cookieToButtonMapping["33_21_20_3_2_33_21_20_3_2_"] = Select;
183 cookieToButtonMapping["33_21_20_11_2_33_21_20_11_2_"] = PlayHold;
184}
185
186static io_object_t _findAppleRemoteDevice(const char *devName)
187{
188 CFMutableDictionaryRef hidMatchDictionary = nullptr;
189 io_iterator_t hidObjectIterator = 0;
190 io_object_t hidDevice = 0;
191 IOReturn ioReturnValue;
192
193
194 hidMatchDictionary = IOServiceMatching(devName);
195
196 // check for matching devices
197 ioReturnValue = IOServiceGetMatchingServices(kIOMainPortDefault,
198 hidMatchDictionary,
199 &hidObjectIterator);
200
201 if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0))
202 hidDevice = IOIteratorNext(hidObjectIterator);
203 else
204 LOG(VB_GENERAL, LOG_ERR, LOC +
205 QString("_findAppleRemoteDevice(%1) failed").arg(devName));
206
207 // IOServiceGetMatchingServices consumes a reference to the dictionary,
208 // so we don't need to release the dictionary ref.
209 hidMatchDictionary = nullptr;
210 return hidDevice;
211}
212
214{
215 IOHIDDeviceInterface122** handle;
216 CFArrayRef elements;
217 IOReturn success;
218
219 handle = (IOHIDDeviceInterface122**)hidDeviceInterface;
220 success = (*handle)->copyMatchingElements(handle,
221 nullptr,
222 (CFArrayRef*)&elements);
223
224 if (success == kIOReturnSuccess)
225 {
226 for (CFIndex i = 0; i < CFArrayGetCount(elements); ++i)
227 {
228 CFDictionaryRef element;
229 CFTypeRef object;
230 long number;
231 IOHIDElementCookie cookie;
232
233 element = (CFDictionaryRef)CFArrayGetValueAtIndex(elements, i);
234 object = CFDictionaryGetValue(element,
235 CFSTR(kIOHIDElementCookieKey));
236
237 if (object == nullptr || CFGetTypeID(object) != CFNumberGetTypeID())
238 continue;
239
240 if (!CFNumberGetValue((CFNumberRef)object,
241 kCFNumberLongType, &number))
242 continue;
243
244 cookie = (IOHIDElementCookie)number;
245
246 cookies.push_back((int)cookie);
247 }
248 return true;
249 }
250 return false;
251}
252
253bool AppleRemote::_createDeviceInterface(io_object_t hidDevice)
254{
255 IOReturn ioReturnValue;
256 IOCFPlugInInterface** plugInInterface = nullptr;
257 SInt32 score = 0;
258
259
260 ioReturnValue
261 = IOCreatePlugInInterfaceForService(hidDevice,
262 kIOHIDDeviceUserClientTypeID,
263 kIOCFPlugInInterfaceID,
264 &plugInInterface, &score);
265
266 if ((kIOReturnSuccess == ioReturnValue) &&
267 plugInInterface && *plugInInterface)
268 {
269 HRESULT plugInResult = (*plugInInterface)->QueryInterface
270 (plugInInterface,
271 CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID),
272 (LPVOID*) (&hidDeviceInterface));
273
274 if (plugInResult != S_OK)
275 LOG(VB_GENERAL, LOG_ERR, LOC + "_createDeviceInterface() failed");
276
277 (*plugInInterface)->Release(plugInInterface);
278 }
279 return hidDeviceInterface != nullptr;
280}
281
283{
284 CFRunLoopSourceRef eventSource;
285 IOReturn ioReturnValue;
286 IOHIDOptionsType openMode;
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
342void AppleRemote::QueueCallbackFunction(void* target, IOReturn result,
343 void* refcon, void* sender)
344{
345 auto* remote = static_cast<AppleRemote*>(target);
346
347 remote->_queueCallbackFunction(result, refcon, sender);
348}
349
351 void* /*refcon*/, void* /*sender*/)
352{
353 AbsoluteTime zeroTime = {0,0};
354 SInt32 sumOfValues = 0;
355 std::stringstream cookieString;
356
357 while (result == kIOReturnSuccess)
358 {
359 IOHIDEventStruct event;
360
361 result = (*queue)->getNextEvent(queue, &event, zeroTime, 0);
362 if (result != kIOReturnSuccess)
363 break;
364
365 if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie)
366 {
367 remoteId=event.value;
369 }
370 else
371 {
372 sumOfValues+=event.value;
373 cookieString << std::dec << (int)event.elementCookie << "_";
374 }
375 }
376
377 _handleEventWithCookieString(cookieString.str(), sumOfValues);
378}
379
380void AppleRemote::_handleEventWithCookieString(std::string cookieString,
381 SInt32 sumOfValues)
382{
383 std::map<std::string,AppleRemote::Event>::iterator ii;
384
385 ii = cookieToButtonMapping.find(cookieString);
386 if (ii != cookieToButtonMapping.end() && _listener)
387 {
388 AppleRemote::Event buttonid = ii->second;
389 if (_listener)
390 _listener->appleRemoteButton(buttonid, sumOfValues>0);
391 }
392}
#define LOC
Definition: AppleRemote.cpp:38
#define REMOTE_COOKIE_STR
Definition: AppleRemote.cpp:35
static io_object_t _findAppleRemoteDevice(const char *devName)
#define REMOTE_SWITCH_COOKIE
Definition: AppleRemote.cpp:34
#define kIOMainPortDefault
Definition: AppleRemote.cpp:28
virtual void appleRemoteButton(Event button, bool pressedDown)=0
void _handleEventWithCookieString(std::string cookieString, SInt32 sumOfValues)
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:69
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:42
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:64
void stopListening()
Definition: AppleRemote.cpp:96
static void QueueCallbackFunction(void *target, IOReturn result, void *refcon, void *sender)
void startListening()
Definition: AppleRemote.cpp:74
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
bool isRunning(void) const
Definition: mthread.cpp:263
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
int exec(void)
Enters the qt event loop. call exit or quit to exit thread.
Definition: mthread.cpp:325
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
Definition: mthread.cpp:278
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39