VST3: fetch midi mappings all at once, use it for note/sound-off
[carla.git] / source / modules / juce_gui_extra / native / juce_mac_AppleRemote.mm
blob014fa7f01324cd447a9aaf3728e3d1e9e20a49db
1 /*\r
2   ==============================================================================\r
3 \r
4    This file is part of the JUCE library.\r
5    Copyright (c) 2022 - Raw Material Software Limited\r
6 \r
7    JUCE is an open source library subject to commercial or open-source\r
8    licensing.\r
9 \r
10    By using JUCE, you agree to the terms of both the JUCE 7 End-User License\r
11    Agreement and JUCE Privacy Policy.\r
13    End User License Agreement: www.juce.com/juce-7-licence\r
14    Privacy Policy: www.juce.com/juce-privacy-policy\r
16    Or: You may also use this code under the terms of the GPL v3 (see\r
17    www.gnu.org/licenses).\r
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER\r
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE\r
21    DISCLAIMED.\r
23   ==============================================================================\r
24 */\r
26 namespace juce\r
27 {\r
29 AppleRemoteDevice::AppleRemoteDevice()\r
30     : device (nullptr),\r
31       queue (nullptr),\r
32       remoteId (0)\r
33 {\r
34 }\r
36 AppleRemoteDevice::~AppleRemoteDevice()\r
37 {\r
38     stop();\r
39 }\r
41 namespace\r
42 {\r
43     io_object_t getAppleRemoteDevice()\r
44     {\r
45         CFMutableDictionaryRef dict = IOServiceMatching ("AppleIRController");\r
47         io_iterator_t iter = 0;\r
48         io_object_t iod = 0;\r
50         const auto defaultPort = []\r
51         {\r
52            #if defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0\r
53             if (@available (macOS 12.0, *))\r
54                 return kIOMainPortDefault;\r
55            #endif\r
57             JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")\r
58             return kIOMasterPortDefault;\r
59             JUCE_END_IGNORE_WARNINGS_GCC_LIKE\r
60         }();\r
62         if (IOServiceGetMatchingServices (defaultPort, dict, &iter) == kIOReturnSuccess\r
63              && iter != 0)\r
64         {\r
65             iod = IOIteratorNext (iter);\r
66         }\r
68         IOObjectRelease (iter);\r
69         return iod;\r
70     }\r
72     bool createAppleRemoteInterface (io_object_t iod, void** device)\r
73     {\r
74         jassert (*device == nullptr);\r
75         io_name_t classname;\r
77         if (IOObjectGetClass (iod, classname) == kIOReturnSuccess)\r
78         {\r
79             IOCFPlugInInterface** cfPlugInInterface = nullptr;\r
80             SInt32 score = 0;\r
82             if (IOCreatePlugInInterfaceForService (iod,\r
83                                                    kIOHIDDeviceUserClientTypeID,\r
84                                                    kIOCFPlugInInterfaceID,\r
85                                                    &cfPlugInInterface,\r
86                                                    &score) == kIOReturnSuccess)\r
87             {\r
88                 HRESULT hr = (*cfPlugInInterface)->QueryInterface (cfPlugInInterface,\r
89                                                                    CFUUIDGetUUIDBytes (kIOHIDDeviceInterfaceID),\r
90                                                                    device);\r
92                 ignoreUnused (hr);\r
94                 (*cfPlugInInterface)->Release (cfPlugInInterface);\r
95             }\r
96         }\r
98         return *device != nullptr;\r
99     }\r
101     void appleRemoteQueueCallback (void* const target, const IOReturn result, void*, void*)\r
102     {\r
103         if (result == kIOReturnSuccess)\r
104             ((AppleRemoteDevice*) target)->handleCallbackInternal();\r
105     }\r
108 bool AppleRemoteDevice::start (const bool inExclusiveMode)\r
110     if (queue != nullptr)\r
111         return true;\r
113     stop();\r
115     bool result = false;\r
116     io_object_t iod = getAppleRemoteDevice();\r
118     if (iod != 0)\r
119     {\r
120         if (createAppleRemoteInterface (iod, &device) && open (inExclusiveMode))\r
121             result = true;\r
122         else\r
123             stop();\r
125         IOObjectRelease (iod);\r
126     }\r
128     return result;\r
131 void AppleRemoteDevice::stop()\r
133     if (queue != nullptr)\r
134     {\r
135         (*(IOHIDQueueInterface**) queue)->stop ((IOHIDQueueInterface**) queue);\r
136         (*(IOHIDQueueInterface**) queue)->dispose ((IOHIDQueueInterface**) queue);\r
137         (*(IOHIDQueueInterface**) queue)->Release ((IOHIDQueueInterface**) queue);\r
138         queue = nullptr;\r
139     }\r
141     if (device != nullptr)\r
142     {\r
143         (*(IOHIDDeviceInterface**) device)->close ((IOHIDDeviceInterface**) device);\r
144         (*(IOHIDDeviceInterface**) device)->Release ((IOHIDDeviceInterface**) device);\r
145         device = nullptr;\r
146     }\r
149 bool AppleRemoteDevice::isActive() const\r
151     return queue != nullptr;\r
154 bool AppleRemoteDevice::open (const bool openInExclusiveMode)\r
156     Array<int> cookies;\r
158     CFObjectHolder<CFArrayRef> elements;\r
159     auto device122 = (IOHIDDeviceInterface122**) device;\r
161     if ((*device122)->copyMatchingElements (device122, nullptr, &elements.object) != kIOReturnSuccess)\r
162         return false;\r
164     for (int i = 0; i < CFArrayGetCount (elements.object); ++i)\r
165     {\r
166         auto element = (CFDictionaryRef) CFArrayGetValueAtIndex (elements.object, i);\r
168         // get the cookie\r
169         CFTypeRef object = CFDictionaryGetValue (element, CFSTR (kIOHIDElementCookieKey));\r
171         if (object == nullptr || CFGetTypeID (object) != CFNumberGetTypeID())\r
172             continue;\r
174         long number;\r
175         if (! CFNumberGetValue ((CFNumberRef) object, kCFNumberLongType, &number))\r
176             continue;\r
178         cookies.add ((int) number);\r
179     }\r
181     if ((*(IOHIDDeviceInterface**) device)\r
182             ->open ((IOHIDDeviceInterface**) device,\r
183                     openInExclusiveMode ? kIOHIDOptionsTypeSeizeDevice\r
184                                         : kIOHIDOptionsTypeNone) == KERN_SUCCESS)\r
185     {\r
186         queue = (*(IOHIDDeviceInterface**) device)->allocQueue ((IOHIDDeviceInterface**) device);\r
188         if (queue != nullptr)\r
189         {\r
190             (*(IOHIDQueueInterface**) queue)->create ((IOHIDQueueInterface**) queue, 0, 12);\r
192             for (int i = 0; i < cookies.size(); ++i)\r
193             {\r
194                 IOHIDElementCookie cookie = (IOHIDElementCookie) cookies.getUnchecked(i);\r
195                 (*(IOHIDQueueInterface**) queue)->addElement ((IOHIDQueueInterface**) queue, cookie, 0);\r
196             }\r
198             CFRunLoopSourceRef eventSource;\r
200             if ((*(IOHIDQueueInterface**) queue)\r
201                     ->createAsyncEventSource ((IOHIDQueueInterface**) queue, &eventSource) == KERN_SUCCESS)\r
202             {\r
203                 if ((*(IOHIDQueueInterface**) queue)->setEventCallout ((IOHIDQueueInterface**) queue,\r
204                                                                        appleRemoteQueueCallback, this, nullptr) == KERN_SUCCESS)\r
205                 {\r
206                     CFRunLoopAddSource (CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);\r
208                     (*(IOHIDQueueInterface**) queue)->start ((IOHIDQueueInterface**) queue);\r
210                     return true;\r
211                 }\r
212             }\r
213         }\r
214     }\r
216     return false;\r
219 void AppleRemoteDevice::handleCallbackInternal()\r
221     int totalValues = 0;\r
222     AbsoluteTime nullTime = { 0, 0 };\r
223     char cookies [12];\r
224     int numCookies = 0;\r
226     while (numCookies < numElementsInArray (cookies))\r
227     {\r
228         IOHIDEventStruct e;\r
230         if ((*(IOHIDQueueInterface**) queue)->getNextEvent ((IOHIDQueueInterface**) queue, &e, nullTime, 0) != kIOReturnSuccess)\r
231             break;\r
233         if ((int) e.elementCookie == 19)\r
234         {\r
235             remoteId = e.value;\r
236             buttonPressed (switched, false);\r
237         }\r
238         else\r
239         {\r
240             totalValues += e.value;\r
241             cookies [numCookies++] = (char) (pointer_sized_int) e.elementCookie;\r
242         }\r
243     }\r
245     cookies [numCookies++] = 0;\r
247     static const char buttonPatterns[] =\r
248     {\r
249         0x1f, 0x14, 0x12, 0x1f, 0x14, 0x12, 0,\r
250         0x1f, 0x15, 0x12, 0x1f, 0x15, 0x12, 0,\r
251         0x1f, 0x1d, 0x1c, 0x12, 0,\r
252         0x1f, 0x1e, 0x1c, 0x12, 0,\r
253         0x1f, 0x16, 0x12, 0x1f, 0x16, 0x12, 0,\r
254         0x1f, 0x17, 0x12, 0x1f, 0x17, 0x12, 0,\r
255         0x1f, 0x12, 0x04, 0x02, 0,\r
256         0x1f, 0x12, 0x03, 0x02, 0,\r
257         0x1f, 0x12, 0x1f, 0x12, 0,\r
258         0x23, 0x1f, 0x12, 0x23, 0x1f, 0x12, 0,\r
259         19, 0\r
260     };\r
262     int buttonNum = (int) menuButton;\r
263     int i = 0;\r
265     while (i < numElementsInArray (buttonPatterns))\r
266     {\r
267         if (strcmp (cookies, buttonPatterns + i) == 0)\r
268         {\r
269             buttonPressed ((ButtonType) buttonNum, totalValues > 0);\r
270             break;\r
271         }\r
273         i += (int) strlen (buttonPatterns + i) + 1;\r
274         ++buttonNum;\r
275     }\r
278 } // namespace juce\r