VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.cpp@ 103131

Last change on this file since 103131 was 100108, checked in by vboxsync, 22 months ago

*: Fix build issues when setting VBOX_WITH_WARNINGS_AS_ERRORS=1 on darwin.arm64 and make it a default, bugref:10469

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 86.1 KB
Line 
1/* $Id: DarwinKeyboard.cpp 100108 2023-06-07 20:05:13Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - Declarations of utility functions for handling Darwin Keyboard specific tasks.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Defines: */
29#define LOG_GROUP LOG_GROUP_GUI
30#define VBOX_WITH_KBD_LEDS_SYNC
31//#define VBOX_WITHOUT_KBD_LEDS_SYNC_FILTERING
32
33/* GUI includes: */
34#include "DarwinKeyboard.h"
35#ifndef USE_HID_FOR_MODIFIERS
36# include "CocoaEventHelper.h"
37#endif
38
39/* Other VBox includes: */
40#include <iprt/assert.h>
41#include <iprt/asm.h>
42#include <iprt/mem.h>
43#include <iprt/time.h>
44#include <VBox/log.h>
45#ifdef DEBUG_PRINTF
46# include <iprt/stream.h>
47#endif
48#ifdef VBOX_WITH_KBD_LEDS_SYNC
49# include <iprt/errcore.h>
50# include <iprt/semaphore.h>
51# include <VBox/sup.h>
52#endif
53
54/* External includes: */
55#include <ApplicationServices/ApplicationServices.h>
56#include <Carbon/Carbon.h>
57#include <IOKit/IOCFPlugIn.h>
58#include <IOKit/IOKitLib.h>
59#include <IOKit/hid/IOHIDLib.h>
60#include <IOKit/usb/USB.h>
61#ifdef USE_HID_FOR_MODIFIERS
62# include <CoreFoundation/CoreFoundation.h>
63# include <IOKit/hid/IOHIDUsageTables.h>
64# include <mach/mach.h>
65# include <mach/mach_error.h>
66#endif
67#ifdef VBOX_WITH_KBD_LEDS_SYNC
68# include <IOKit/IOMessage.h>
69# include <IOKit/usb/IOUSBLib.h>
70#endif
71
72
73RT_C_DECLS_BEGIN
74/* Private interface in 10.3 and later. */
75typedef int CGSConnection;
76typedef enum
77{
78 kCGSGlobalHotKeyEnable = 0,
79 kCGSGlobalHotKeyDisable,
80 kCGSGlobalHotKeyDisableExceptUniversalAccess,
81 kCGSGlobalHotKeyInvalid = -1 /* bird */
82} CGSGlobalHotKeyOperatingMode;
83extern CGSConnection _CGSDefaultConnection(void);
84extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection Connection, CGSGlobalHotKeyOperatingMode *enmMode);
85extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection Connection, CGSGlobalHotKeyOperatingMode enmMode);
86RT_C_DECLS_END
87
88
89/* Defined Constants And Macros: */
90#define QZ_RMETA 0x36
91#define QZ_LMETA 0x37
92#define QZ_LSHIFT 0x38
93#define QZ_CAPSLOCK 0x39
94#define QZ_LALT 0x3A
95#define QZ_LCTRL 0x3B
96#define QZ_RSHIFT 0x3C
97#define QZ_RALT 0x3D
98#define QZ_RCTRL 0x3E
99// Found the definition of the fn-key in:
100// http://stuff.mit.edu/afs/sipb/project/darwin/src/modules/IOHIDFamily/IOHIDSystem/IOHIKeyboardMapper.cpp &
101// http://stuff.mit.edu/afs/sipb/project/darwin/src/modules/AppleADBKeyboard/AppleADBKeyboard.cpp
102// Maybe we need this in the future.
103#define QZ_FN 0x3F
104#define QZ_NUMLOCK 0x47
105/** Short hand for an extended key. */
106#define K_EX VBOXKEY_EXTENDED
107/** Short hand for a modifier key. */
108#define K_MOD VBOXKEY_MODIFIER
109/** Short hand for a lock key. */
110#define K_LOCK VBOXKEY_LOCK
111#ifdef USE_HID_FOR_MODIFIERS
112/** An attempt at catching reference leaks. */
113# define MY_CHECK_CREFS(cRefs) do { AssertMsg(cRefs < 25, ("%ld\n", cRefs)); NOREF(cRefs); } while (0)
114#endif
115
116
117/** This is derived partially from SDL_QuartzKeys.h and partially from testing.
118 * (The funny thing about the virtual scan codes on the mac is that they aren't
119 * offically documented, which is rather silly to say the least. Thus, the need
120 * for looking at SDL and other odd places for docs.) */
121static const uint16_t g_aDarwinToSet1[] =
122{
123 /* set-1 SDL_QuartzKeys.h */
124 0x1e, /* QZ_a 0x00 */
125 0x1f, /* QZ_s 0x01 */
126 0x20, /* QZ_d 0x02 */
127 0x21, /* QZ_f 0x03 */
128 0x23, /* QZ_h 0x04 */
129 0x22, /* QZ_g 0x05 */
130 0x2c, /* QZ_z 0x06 */
131 0x2d, /* QZ_x 0x07 */
132 0x2e, /* QZ_c 0x08 */
133 0x2f, /* QZ_v 0x09 */
134 0x56, /* between lshift and z. 'INT 1'? */
135 0x30, /* QZ_b 0x0B */
136 0x10, /* QZ_q 0x0C */
137 0x11, /* QZ_w 0x0D */
138 0x12, /* QZ_e 0x0E */
139 0x13, /* QZ_r 0x0F */
140 0x15, /* QZ_y 0x10 */
141 0x14, /* QZ_t 0x11 */
142 0x02, /* QZ_1 0x12 */
143 0x03, /* QZ_2 0x13 */
144 0x04, /* QZ_3 0x14 */
145 0x05, /* QZ_4 0x15 */
146 0x07, /* QZ_6 0x16 */
147 0x06, /* QZ_5 0x17 */
148 0x0d, /* QZ_EQUALS 0x18 */
149 0x0a, /* QZ_9 0x19 */
150 0x08, /* QZ_7 0x1A */
151 0x0c, /* QZ_MINUS 0x1B */
152 0x09, /* QZ_8 0x1C */
153 0x0b, /* QZ_0 0x1D */
154 0x1b, /* QZ_RIGHTBRACKET 0x1E */
155 0x18, /* QZ_o 0x1F */
156 0x16, /* QZ_u 0x20 */
157 0x1a, /* QZ_LEFTBRACKET 0x21 */
158 0x17, /* QZ_i 0x22 */
159 0x19, /* QZ_p 0x23 */
160 0x1c, /* QZ_RETURN 0x24 */
161 0x26, /* QZ_l 0x25 */
162 0x24, /* QZ_j 0x26 */
163 0x28, /* QZ_QUOTE 0x27 */
164 0x25, /* QZ_k 0x28 */
165 0x27, /* QZ_SEMICOLON 0x29 */
166 0x2b, /* QZ_BACKSLASH 0x2A */
167 0x33, /* QZ_COMMA 0x2B */
168 0x35, /* QZ_SLASH 0x2C */
169 0x31, /* QZ_n 0x2D */
170 0x32, /* QZ_m 0x2E */
171 0x34, /* QZ_PERIOD 0x2F */
172 0x0f, /* QZ_TAB 0x30 */
173 0x39, /* QZ_SPACE 0x31 */
174 0x29, /* QZ_BACKQUOTE 0x32 */
175 0x0e, /* QZ_BACKSPACE 0x33 */
176 0x9c, /* QZ_IBOOK_ENTER 0x34 */
177 0x01, /* QZ_ESCAPE 0x35 */
178 0x5c|K_EX|K_MOD, /* QZ_RMETA 0x36 */
179 0x5b|K_EX|K_MOD, /* QZ_LMETA 0x37 */
180 0x2a|K_MOD, /* QZ_LSHIFT 0x38 */
181 0x3a|K_LOCK, /* QZ_CAPSLOCK 0x39 */
182 0x38|K_MOD, /* QZ_LALT 0x3A */
183 0x1d|K_MOD, /* QZ_LCTRL 0x3B */
184 0x36|K_MOD, /* QZ_RSHIFT 0x3C */
185 0x38|K_EX|K_MOD, /* QZ_RALT 0x3D */
186 0x1d|K_EX|K_MOD, /* QZ_RCTRL 0x3E */
187 0, /* */
188 0, /* */
189 0x53, /* QZ_KP_PERIOD 0x41 */
190 0, /* */
191 0x37, /* QZ_KP_MULTIPLY 0x43 */
192 0, /* */
193 0x4e, /* QZ_KP_PLUS 0x45 */
194 0, /* */
195 0x45|K_LOCK, /* QZ_NUMLOCK 0x47 */
196 0, /* */
197 0, /* */
198 0, /* */
199 0x35|K_EX, /* QZ_KP_DIVIDE 0x4B */
200 0x1c|K_EX, /* QZ_KP_ENTER 0x4C */
201 0, /* */
202 0x4a, /* QZ_KP_MINUS 0x4E */
203 0, /* */
204 0, /* */
205 0x0d/*?*/, /* QZ_KP_EQUALS 0x51 */
206 0x52, /* QZ_KP0 0x52 */
207 0x4f, /* QZ_KP1 0x53 */
208 0x50, /* QZ_KP2 0x54 */
209 0x51, /* QZ_KP3 0x55 */
210 0x4b, /* QZ_KP4 0x56 */
211 0x4c, /* QZ_KP5 0x57 */
212 0x4d, /* QZ_KP6 0x58 */
213 0x47, /* QZ_KP7 0x59 */
214 0, /* */
215 0x48, /* QZ_KP8 0x5B */
216 0x49, /* QZ_KP9 0x5C */
217 0x7d, /* yen, | (JIS) 0x5D */
218 0x73, /* _, ro (JIS) 0x5E */
219 0, /* */
220 0x3f, /* QZ_F5 0x60 */
221 0x40, /* QZ_F6 0x61 */
222 0x41, /* QZ_F7 0x62 */
223 0x3d, /* QZ_F3 0x63 */
224 0x42, /* QZ_F8 0x64 */
225 0x43, /* QZ_F9 0x65 */
226 0x29, /* Zen/Han (JIS) 0x66 */
227 0x57, /* QZ_F11 0x67 */
228 0x29, /* Zen/Han (JIS) 0x68 */
229 0x37|K_EX, /* QZ_PRINT / F13 0x69 */
230 0x63, /* QZ_F16 0x6A */
231 0x46|K_LOCK, /* QZ_SCROLLOCK 0x6B */
232 0, /* */
233 0x44, /* QZ_F10 0x6D */
234 0x5d|K_EX, /* */
235 0x58, /* QZ_F12 0x6F */
236 0, /* */
237 0/* 0xe1,0x1d,0x45*/, /* QZ_PAUSE 0x71 */
238 0x52|K_EX, /* QZ_INSERT / HELP 0x72 */
239 0x47|K_EX, /* QZ_HOME 0x73 */
240 0x49|K_EX, /* QZ_PAGEUP 0x74 */
241 0x53|K_EX, /* QZ_DELETE 0x75 */
242 0x3e, /* QZ_F4 0x76 */
243 0x4f|K_EX, /* QZ_END 0x77 */
244 0x3c, /* QZ_F2 0x78 */
245 0x51|K_EX, /* QZ_PAGEDOWN 0x79 */
246 0x3b, /* QZ_F1 0x7A */
247 0x4b|K_EX, /* QZ_LEFT 0x7B */
248 0x4d|K_EX, /* QZ_RIGHT 0x7C */
249 0x50|K_EX, /* QZ_DOWN 0x7D */
250 0x48|K_EX, /* QZ_UP 0x7E */
251 0,/*0x5e|K_EX*/ /* QZ_POWER 0x7F */ /* have different break key! */
252 /* do NEVER deliver the Power
253 * scancode as e.g. Windows will
254 * handle it, @bugref{7692}. */
255};
256
257
258/** Holds whether we've connected or not. */
259static bool g_fConnectedToCGS = false;
260/** Holds the cached connection. */
261static CGSConnection g_CGSConnection;
262
263
264#ifdef USE_HID_FOR_MODIFIERS
265
266/** Holds the IO Master Port. */
267static mach_port_t g_MasterPort = NULL;
268
269/** Holds the amount of keyboards in the cache. */
270static unsigned g_cKeyboards = 0;
271/** Array of cached keyboard data. */
272static struct KeyboardCacheData
273{
274 /** The device interface. */
275 IOHIDDeviceInterface **ppHidDeviceInterface;
276 /** The queue interface. */
277 IOHIDQueueInterface **ppHidQueueInterface;
278
279 /** Cookie translation array. */
280 struct KeyboardCacheCookie
281 {
282 /** The cookie. */
283 IOHIDElementCookie Cookie;
284 /** The corresponding modifier mask. */
285 uint32_t fMask;
286 } aCookies[64];
287 /** Number of cookies in the array. */
288 unsigned cCookies;
289} g_aKeyboards[128];
290/** Holds the keyboard cache creation timestamp. */
291static uint64_t g_u64KeyboardTS = 0;
292
293/** Holds the HID queue status. */
294static bool g_fHIDQueueEnabled;
295/** Holds the current modifier mask. */
296static uint32_t g_fHIDModifierMask;
297/** Holds the old modifier mask. */
298static uint32_t g_fOldHIDModifierMask;
299
300#endif /* USE_HID_FOR_MODIFIERS */
301
302
303#ifdef VBOX_WITH_KBD_LEDS_SYNC
304
305#define VBOX_BOOL_TO_STR_STATE(x) (x) ? "ON" : "OFF"
306/** HID LEDs synchronization data: LED states. */
307typedef struct VBoxLedState_t
308{
309 /** Holds the state of NUM LOCK. */
310 bool fNumLockOn;
311 /** Holds the state of CAPS LOCK. */
312 bool fCapsLockOn;
313 /** Holds the state of SCROLL LOCK. */
314 bool fScrollLockOn;
315} VBoxLedState_t;
316
317/** HID LEDs synchronization data: keyboard states. */
318typedef struct VBoxKbdState_t
319{
320 /** Holds the reference to IOKit HID device. */
321 IOHIDDeviceRef pDevice;
322 /** Holds the LED states. */
323 VBoxLedState_t LED;
324 /** Holds the pointer to a VBoxHidsState_t instance where VBoxKbdState_t instance is stored. */
325 void *pParentContainer;
326 /** Holds the position in global storage (used to simplify CFArray navigation when removing detached device). */
327 CFIndex idxPosition;
328 /** Holds the KBD CAPS LOCK key hold timeout (some Apple keyboards only). */
329 uint64_t cCapsLockTimeout;
330 /** Holds the HID Location ID: unique for an USB device registered in the system. */
331 uint32_t idLocation;
332} VBoxKbdState_t;
333
334/** A struct that used to pass input event info from IOKit callback to a Carbon one */
335typedef struct VBoxKbdEvent_t
336{
337 VBoxKbdState_t *pKbd;
338 uint32_t iKeyCode;
339 uint64_t tsKeyDown;
340} VBoxKbdEvent_t;
341
342/** HID LEDs synchronization data: IOKit specific data. */
343typedef struct VBoxHidsState_t
344{
345 /** Holds the IOKit HID manager reference. */
346 IOHIDManagerRef hidManagerRef;
347 /** Holds the array which consists of VBoxKbdState_t elements. */
348 CFMutableArrayRef pDeviceCollection;
349 /** Holds the LED states that were stored during last broadcast and reflect a guest LED states. */
350 VBoxLedState_t guestState;
351
352 /** Holds the queue which will be appended in IOKit input callback. Carbon input callback will extract data from it. */
353 CFMutableArrayRef pFifoEventQueue;
354 /** Holds the lock for pFifoEventQueue. */
355 RTSEMMUTEX fifoEventQueueLock;
356
357 /** Holds the IOService notification reference: USB HID device matching. */
358 io_iterator_t pUsbHidDeviceMatchNotify;
359 /** Holds the IOService notification reference: USB HID general interest notifications (IOService messages). */
360 io_iterator_t pUsbHidGeneralInterestNotify;
361 /** Holds the IOService notification port reference: device match and device general interest message. */
362 IONotificationPortRef pNotificationPrortRef;
363
364 CFMachPortRef pTapRef;
365 CFRunLoopSourceRef pLoopSourceRef;
366} VBoxHidsState_t;
367
368#endif /* VBOX_WITH_KBD_LEDS_SYNC */
369
370
371unsigned DarwinKeycodeToSet1Scancode(unsigned uKeyCode)
372{
373 if (uKeyCode >= RT_ELEMENTS(g_aDarwinToSet1))
374 return 0;
375 return g_aDarwinToSet1[uKeyCode];
376}
377
378UInt32 DarwinAdjustModifierMask(UInt32 fModifiers, const void *pvCocoaEvent)
379{
380 /* Check if there is anything to adjust and perform the adjustment. */
381 if (fModifiers & (shiftKey | rightShiftKey | controlKey | rightControlKey | optionKey | rightOptionKey | cmdKey | kEventKeyModifierRightCmdKeyMask))
382 {
383#ifndef USE_HID_FOR_MODIFIERS
384 // WORKAROUND:
385 // Convert the Cocoa modifiers to Carbon ones (the Cocoa modifier
386 // definitions are tucked away in Objective-C headers, unfortunately).
387 //
388 // Update: CGEventTypes.h includes what looks like the Cocoa modifiers
389 // and the NX_* defines should be available as well. We should look
390 // into ways to intercept the CG (core graphics) events in the Carbon
391 // based setup and get rid of all this HID mess. */
392 AssertPtr(pvCocoaEvent);
393 //::darwinPrintEvent("dbg-adjMods: ", pvCocoaEvent);
394 uint32_t fAltModifiers = ::darwinEventModifierFlagsXlated(pvCocoaEvent);
395#else /* USE_HID_FOR_MODIFIERS */
396 /* Update the keyboard cache. */
397 darwinHIDKeyboardCacheUpdate();
398 const UInt32 fAltModifiers = g_fHIDModifierMask;
399#endif /* USE_HID_FOR_MODIFIERS */
400
401#ifdef DEBUG_PRINTF
402 RTPrintf("dbg-fAltModifiers=%#x fModifiers=%#x", fAltModifiers, fModifiers);
403#endif
404 if ( (fModifiers & (rightShiftKey | shiftKey))
405 && (fAltModifiers & (rightShiftKey | shiftKey)))
406 {
407 fModifiers &= ~(rightShiftKey | shiftKey);
408 fModifiers |= fAltModifiers & (rightShiftKey | shiftKey);
409 }
410
411 if ( (fModifiers & (rightControlKey | controlKey))
412 && (fAltModifiers & (rightControlKey | controlKey)))
413 {
414 fModifiers &= ~(rightControlKey | controlKey);
415 fModifiers |= fAltModifiers & (rightControlKey | controlKey);
416 }
417
418 if ( (fModifiers & (optionKey | rightOptionKey))
419 && (fAltModifiers & (optionKey | rightOptionKey)))
420 {
421 fModifiers &= ~(optionKey | rightOptionKey);
422 fModifiers |= fAltModifiers & (optionKey | rightOptionKey);
423 }
424
425 if ( (fModifiers & (cmdKey | kEventKeyModifierRightCmdKeyMask))
426 && (fAltModifiers & (cmdKey | kEventKeyModifierRightCmdKeyMask)))
427 {
428 fModifiers &= ~(cmdKey | kEventKeyModifierRightCmdKeyMask);
429 fModifiers |= fAltModifiers & (cmdKey | kEventKeyModifierRightCmdKeyMask);
430 }
431#ifdef DEBUG_PRINTF
432 RTPrintf(" -> %#x\n", fModifiers);
433#endif
434 }
435 return fModifiers;
436}
437
438unsigned DarwinModifierMaskToSet1Scancode(UInt32 fModifiers)
439{
440 unsigned uScanCode = DarwinModifierMaskToDarwinKeycode(fModifiers);
441 if (uScanCode < RT_ELEMENTS(g_aDarwinToSet1))
442 uScanCode = g_aDarwinToSet1[uScanCode];
443 else
444 Assert(uScanCode == ~0U);
445 return uScanCode;
446}
447
448unsigned DarwinModifierMaskToDarwinKeycode(UInt32 fModifiers)
449{
450 unsigned uKeyCode;
451
452 /** @todo find symbols for these keycodes... */
453 fModifiers &= shiftKey | rightShiftKey | controlKey | rightControlKey | optionKey | rightOptionKey | cmdKey
454 | kEventKeyModifierRightCmdKeyMask | kEventKeyModifierNumLockMask | alphaLock | kEventKeyModifierFnMask;
455 if (fModifiers == shiftKey)
456 uKeyCode = QZ_LSHIFT;
457 else if (fModifiers == rightShiftKey)
458 uKeyCode = QZ_RSHIFT;
459 else if (fModifiers == controlKey)
460 uKeyCode = QZ_LCTRL;
461 else if (fModifiers == rightControlKey)
462 uKeyCode = QZ_RCTRL;
463 else if (fModifiers == optionKey)
464 uKeyCode = QZ_LALT;
465 else if (fModifiers == rightOptionKey)
466 uKeyCode = QZ_RALT;
467 else if (fModifiers == cmdKey)
468 uKeyCode = QZ_LMETA;
469 else if (fModifiers == kEventKeyModifierRightCmdKeyMask /* hack */)
470 uKeyCode = QZ_RMETA;
471 else if (fModifiers == alphaLock)
472 uKeyCode = QZ_CAPSLOCK;
473 else if (fModifiers == kEventKeyModifierNumLockMask)
474 uKeyCode = QZ_NUMLOCK;
475 else if (fModifiers == kEventKeyModifierFnMask)
476 uKeyCode = QZ_FN;
477 else if (fModifiers == 0)
478 uKeyCode = 0;
479 else
480 uKeyCode = ~0U; /* multiple */
481 return uKeyCode;
482}
483
484UInt32 DarwinKeyCodeToDarwinModifierMask(unsigned uKeyCode)
485{
486 UInt32 fModifiers;
487
488 /** @todo find symbols for these keycodes... */
489 if (uKeyCode == QZ_LSHIFT)
490 fModifiers = shiftKey;
491 else if (uKeyCode == QZ_RSHIFT)
492 fModifiers = rightShiftKey;
493 else if (uKeyCode == QZ_LCTRL)
494 fModifiers = controlKey;
495 else if (uKeyCode == QZ_RCTRL)
496 fModifiers = rightControlKey;
497 else if (uKeyCode == QZ_LALT)
498 fModifiers = optionKey;
499 else if (uKeyCode == QZ_RALT)
500 fModifiers = rightOptionKey;
501 else if (uKeyCode == QZ_LMETA)
502 fModifiers = cmdKey;
503 else if (uKeyCode == QZ_RMETA)
504 fModifiers = kEventKeyModifierRightCmdKeyMask; /* hack */
505 else if (uKeyCode == QZ_CAPSLOCK)
506 fModifiers = alphaLock;
507 else if (uKeyCode == QZ_NUMLOCK)
508 fModifiers = kEventKeyModifierNumLockMask;
509 else if (uKeyCode == QZ_FN)
510 fModifiers = kEventKeyModifierFnMask;
511 else
512 fModifiers = 0;
513 return fModifiers;
514}
515
516
517void DarwinDisableGlobalHotKeys(bool fDisable)
518{
519 static unsigned s_cComplaints = 0;
520
521 /* Lazy connect to the core graphics service. */
522 if (!g_fConnectedToCGS)
523 {
524 g_CGSConnection = _CGSDefaultConnection();
525 g_fConnectedToCGS = true;
526 }
527
528 /* Get the current mode. */
529 CGSGlobalHotKeyOperatingMode enmMode = kCGSGlobalHotKeyInvalid;
530 CGSGetGlobalHotKeyOperatingMode(g_CGSConnection, &enmMode);
531 if ( enmMode != kCGSGlobalHotKeyEnable
532 && enmMode != kCGSGlobalHotKeyDisable
533 && enmMode != kCGSGlobalHotKeyDisableExceptUniversalAccess)
534 {
535 AssertMsgFailed(("%d\n", enmMode));
536 if (s_cComplaints++ < 32)
537 LogRel(("DarwinDisableGlobalHotKeys: Unexpected enmMode=%d\n", enmMode));
538 return;
539 }
540
541 /* Calc the new mode. */
542 if (fDisable)
543 {
544 if (enmMode != kCGSGlobalHotKeyEnable)
545 return;
546 enmMode = kCGSGlobalHotKeyDisableExceptUniversalAccess;
547 }
548 else
549 {
550 if (enmMode != kCGSGlobalHotKeyDisableExceptUniversalAccess)
551 return;
552 enmMode = kCGSGlobalHotKeyEnable;
553 }
554
555 /* Try set it and check the actual result. */
556 CGSSetGlobalHotKeyOperatingMode(g_CGSConnection, enmMode);
557 CGSGlobalHotKeyOperatingMode enmNewMode = kCGSGlobalHotKeyInvalid;
558 CGSGetGlobalHotKeyOperatingMode(g_CGSConnection, &enmNewMode);
559 if (enmNewMode != enmMode)
560 {
561 /* If the screensaver kicks in we should ignore failure here. */
562 AssertMsg(enmMode == kCGSGlobalHotKeyEnable, ("enmNewMode=%d enmMode=%d\n", enmNewMode, enmMode));
563 if (s_cComplaints++ < 32)
564 LogRel(("DarwinDisableGlobalHotKeys: Failed to change mode; enmNewMode=%d enmMode=%d\n", enmNewMode, enmMode));
565 }
566}
567
568
569#ifdef USE_HID_FOR_MODIFIERS
570
571/** Callback function for consuming queued events.
572 * @param pvTarget Brings the queue?
573 * @param rcIn Brings what?
574 * @param pvRefcon Brings the pointer to the keyboard cache entry.
575 * @param pvSender Brings what? */
576static void darwinQueueCallback(void *pvTarget, IOReturn rcIn, void *pvRefcon, void *pvSender)
577{
578 struct KeyboardCacheData *pKeyboardEntry = (struct KeyboardCacheData *)pvRefcon;
579 if (!pKeyboardEntry->ppHidQueueInterface)
580 return;
581 NOREF(pvTarget);
582 NOREF(rcIn);
583 NOREF(pvSender);
584
585 /* Consume the events. */
586 g_fOldHIDModifierMask = g_fHIDModifierMask;
587 for (;;)
588 {
589#ifdef DEBUG_PRINTF
590 RTPrintf("dbg-ev: "); RTStrmFlush(g_pStdOut);
591#endif
592 IOHIDEventStruct Event;
593 AbsoluteTime ZeroTime = {0,0};
594 IOReturn rc = (*pKeyboardEntry->ppHidQueueInterface)->getNextEvent(pKeyboardEntry->ppHidQueueInterface,
595 &Event, ZeroTime, 0);
596 if (rc != kIOReturnSuccess)
597 break;
598
599 /* Translate the cookie value to a modifier mask. */
600 uint32_t fMask = 0;
601 unsigned i = pKeyboardEntry->cCookies;
602 while (i-- > 0)
603 {
604 if (pKeyboardEntry->aCookies[i].Cookie == Event.elementCookie)
605 {
606 fMask = pKeyboardEntry->aCookies[i].fMask;
607 break;
608 }
609 }
610
611 /* Adjust the modifier mask. */
612 if (Event.value)
613 g_fHIDModifierMask |= fMask;
614 else
615 g_fHIDModifierMask &= ~fMask;
616#ifdef DEBUG_PRINTF
617 RTPrintf("t=%d c=%#x v=%#x cblv=%d lv=%p m=%#X\n", Event.type, Event.elementCookie, Event.value, Event.longValueSize, Event.value, fMask); RTStrmFlush(g_pStdOut);
618#endif
619 }
620#ifdef DEBUG_PRINTF
621 RTPrintf("dbg-ev: done\n"); RTStrmFlush(g_pStdOut);
622#endif
623}
624
625/* Forward declaration for darwinBruteForcePropertySearch. */
626static void darwinBruteForcePropertySearch(CFDictionaryRef DictRef, struct KeyboardCacheData *pKeyboardEntry);
627
628/** Element enumeration callback. */
629static void darwinBruteForcePropertySearchApplier(const void *pvValue, void *pvCacheEntry)
630{
631 if (CFGetTypeID(pvValue) == CFDictionaryGetTypeID())
632 darwinBruteForcePropertySearch((CFMutableDictionaryRef)pvValue, (struct KeyboardCacheData *)pvCacheEntry);
633}
634
635/** Recurses through the keyboard properties looking for certain keys. */
636static void darwinBruteForcePropertySearch(CFDictionaryRef DictRef, struct KeyboardCacheData *pKeyboardEntry)
637{
638 CFTypeRef ObjRef;
639
640 /* Check for the usage page and usage key we want. */
641 long lUsage;
642 ObjRef = CFDictionaryGetValue(DictRef, CFSTR(kIOHIDElementUsageKey));
643 if ( ObjRef
644 && CFGetTypeID(ObjRef) == CFNumberGetTypeID()
645 && CFNumberGetValue((CFNumberRef)ObjRef, kCFNumberLongType, &lUsage))
646 {
647 switch (lUsage)
648 {
649 case kHIDUsage_KeyboardLeftControl:
650 case kHIDUsage_KeyboardLeftShift:
651 case kHIDUsage_KeyboardLeftAlt:
652 case kHIDUsage_KeyboardLeftGUI:
653 case kHIDUsage_KeyboardRightControl:
654 case kHIDUsage_KeyboardRightShift:
655 case kHIDUsage_KeyboardRightAlt:
656 case kHIDUsage_KeyboardRightGUI:
657 {
658 long lPage;
659 ObjRef = CFDictionaryGetValue(DictRef, CFSTR(kIOHIDElementUsagePageKey));
660 if ( !ObjRef
661 || CFGetTypeID(ObjRef) != CFNumberGetTypeID()
662 || !CFNumberGetValue((CFNumberRef)ObjRef, kCFNumberLongType, &lPage)
663 || lPage != kHIDPage_KeyboardOrKeypad)
664 break;
665
666 if (pKeyboardEntry->cCookies >= RT_ELEMENTS(pKeyboardEntry->aCookies))
667 {
668 AssertMsgFailed(("too many cookies!\n"));
669 break;
670 }
671
672 /* Get the cookie and modifier mask. */
673 long lCookie;
674 ObjRef = CFDictionaryGetValue(DictRef, CFSTR(kIOHIDElementCookieKey));
675 if ( !ObjRef
676 || CFGetTypeID(ObjRef) != CFNumberGetTypeID()
677 || !CFNumberGetValue((CFNumberRef)ObjRef, kCFNumberLongType, &lCookie))
678 break;
679
680 uint32_t fMask;
681 switch (lUsage)
682 {
683 case kHIDUsage_KeyboardLeftControl : fMask = controlKey; break;
684 case kHIDUsage_KeyboardLeftShift : fMask = shiftKey; break;
685 case kHIDUsage_KeyboardLeftAlt : fMask = optionKey; break;
686 case kHIDUsage_KeyboardLeftGUI : fMask = cmdKey; break;
687 case kHIDUsage_KeyboardRightControl: fMask = rightControlKey; break;
688 case kHIDUsage_KeyboardRightShift : fMask = rightShiftKey; break;
689 case kHIDUsage_KeyboardRightAlt : fMask = rightOptionKey; break;
690 case kHIDUsage_KeyboardRightGUI : fMask = kEventKeyModifierRightCmdKeyMask; break;
691 default: AssertMsgFailed(("%ld\n",lUsage)); fMask = 0; break;
692 }
693
694 /* If we've got a queue, add the cookie to the queue. */
695 if (pKeyboardEntry->ppHidQueueInterface)
696 {
697 IOReturn rc = (*pKeyboardEntry->ppHidQueueInterface)->addElement(pKeyboardEntry->ppHidQueueInterface, (IOHIDElementCookie)lCookie, 0);
698 AssertMsg(rc == kIOReturnSuccess, ("rc=%d\n", rc));
699#ifdef DEBUG_PRINTF
700 RTPrintf("dbg-add: u=%#lx c=%#lx\n", lUsage, lCookie);
701#endif
702 }
703
704 /* Add the cookie to the keyboard entry. */
705 pKeyboardEntry->aCookies[pKeyboardEntry->cCookies].Cookie = (IOHIDElementCookie)lCookie;
706 pKeyboardEntry->aCookies[pKeyboardEntry->cCookies].fMask = fMask;
707 ++pKeyboardEntry->cCookies;
708 break;
709 }
710 }
711 }
712
713
714 /* Get the elements key and recursively iterate the elements looking for they key cookies. */
715 ObjRef = CFDictionaryGetValue(DictRef, CFSTR(kIOHIDElementKey));
716 if ( ObjRef
717 && CFGetTypeID(ObjRef) == CFArrayGetTypeID())
718 {
719 CFArrayRef ArrayObjRef = (CFArrayRef)ObjRef;
720 CFRange Range = {0, CFArrayGetCount(ArrayObjRef)};
721 CFArrayApplyFunction(ArrayObjRef, Range, darwinBruteForcePropertySearchApplier, pKeyboardEntry);
722 }
723}
724
725/** Creates a keyboard cache entry.
726 * @param pKeyboardEntry Brings the pointer to the entry.
727 * @param KeyboardDevice Brings the keyboard device to create the entry for. */
728static bool darwinHIDKeyboardCacheCreateEntry(struct KeyboardCacheData *pKeyboardEntry, io_object_t KeyboardDevice)
729{
730 unsigned long cRefs = 0;
731 memset(pKeyboardEntry, 0, sizeof(*pKeyboardEntry));
732
733 /* Query the HIDDeviceInterface for this HID (keyboard) object. */
734 SInt32 Score = 0;
735 IOCFPlugInInterface **ppPlugInInterface = NULL;
736 IOReturn rc = IOCreatePlugInInterfaceForService(KeyboardDevice, kIOHIDDeviceUserClientTypeID,
737 kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score);
738 if (rc == kIOReturnSuccess)
739 {
740 IOHIDDeviceInterface **ppHidDeviceInterface = NULL;
741 HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
742 CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID),
743 (LPVOID *)&ppHidDeviceInterface);
744 cRefs = (*ppPlugInInterface)->Release(ppPlugInInterface); MY_CHECK_CREFS(cRefs);
745 ppPlugInInterface = NULL;
746 if (hrc == S_OK)
747 {
748 rc = (*ppHidDeviceInterface)->open(ppHidDeviceInterface, 0);
749 if (rc == kIOReturnSuccess)
750 {
751 /* Create a removal callback. */
752 /** @todo */
753
754 /* Create the queue so we can insert elements while searching the properties. */
755 IOHIDQueueInterface **ppHidQueueInterface = (*ppHidDeviceInterface)->allocQueue(ppHidDeviceInterface);
756 if (ppHidQueueInterface)
757 {
758 rc = (*ppHidQueueInterface)->create(ppHidQueueInterface, 0, 32);
759 if (rc != kIOReturnSuccess)
760 {
761 AssertMsgFailed(("rc=%d\n", rc));
762 cRefs = (*ppHidQueueInterface)->Release(ppHidQueueInterface); MY_CHECK_CREFS(cRefs);
763 ppHidQueueInterface = NULL;
764 }
765 }
766 else
767 AssertFailed();
768 pKeyboardEntry->ppHidQueueInterface = ppHidQueueInterface;
769
770 /* Brute force getting of attributes. */
771 /** @todo read up on how to do this in a less resource intensive way! Suggestions are welcome! */
772 CFMutableDictionaryRef PropertiesRef = 0;
773 kern_return_t krc = IORegistryEntryCreateCFProperties(KeyboardDevice, &PropertiesRef, kCFAllocatorDefault, kNilOptions);
774 if (krc == KERN_SUCCESS)
775 {
776 darwinBruteForcePropertySearch(PropertiesRef, pKeyboardEntry);
777 CFRelease(PropertiesRef);
778 }
779 else
780 AssertMsgFailed(("krc=%#x\n", krc));
781
782 if (ppHidQueueInterface)
783 {
784 /* Now install our queue callback. */
785 CFRunLoopSourceRef RunLoopSrcRef = NULL;
786 rc = (*ppHidQueueInterface)->createAsyncEventSource(ppHidQueueInterface, &RunLoopSrcRef);
787 if (rc == kIOReturnSuccess)
788 {
789 CFRunLoopRef RunLoopRef = (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetMainEventLoop());
790 CFRunLoopAddSource(RunLoopRef, RunLoopSrcRef, kCFRunLoopDefaultMode);
791 }
792
793 /* Now install our queue callback. */
794 rc = (*ppHidQueueInterface)->setEventCallout(ppHidQueueInterface, darwinQueueCallback, ppHidQueueInterface, pKeyboardEntry);
795 if (rc != kIOReturnSuccess)
796 AssertMsgFailed(("rc=%d\n", rc));
797 }
798
799 /* Complete the new keyboard cache entry. */
800 pKeyboardEntry->ppHidDeviceInterface = ppHidDeviceInterface;
801 pKeyboardEntry->ppHidQueueInterface = ppHidQueueInterface;
802 return true;
803 }
804
805 AssertMsgFailed(("rc=%d\n", rc));
806 cRefs = (*ppHidDeviceInterface)->Release(ppHidDeviceInterface); MY_CHECK_CREFS(cRefs);
807 }
808 else
809 AssertMsgFailed(("hrc=%#x\n", hrc));
810 }
811 else
812 AssertMsgFailed(("rc=%d\n", rc));
813
814 return false;
815}
816
817/** Destroys a keyboard cache entry. */
818static void darwinHIDKeyboardCacheDestroyEntry(struct KeyboardCacheData *pKeyboardEntry)
819{
820 unsigned long cRefs;
821
822 /* Destroy the queue. */
823 if (pKeyboardEntry->ppHidQueueInterface)
824 {
825 IOHIDQueueInterface **ppHidQueueInterface = pKeyboardEntry->ppHidQueueInterface;
826 pKeyboardEntry->ppHidQueueInterface = NULL;
827
828 /* Stop it just in case we haven't done so. doesn't really matter I think. */
829 (*ppHidQueueInterface)->stop(ppHidQueueInterface);
830
831 /* Deal with the run loop source. */
832 CFRunLoopSourceRef RunLoopSrcRef = (*ppHidQueueInterface)->getAsyncEventSource(ppHidQueueInterface);
833 if (RunLoopSrcRef)
834 {
835 CFRunLoopRef RunLoopRef = (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetMainEventLoop());
836 CFRunLoopRemoveSource(RunLoopRef, RunLoopSrcRef, kCFRunLoopDefaultMode);
837
838 CFRelease(RunLoopSrcRef);
839 }
840
841 /* Dispose of and release the queue. */
842 (*ppHidQueueInterface)->dispose(ppHidQueueInterface);
843 cRefs = (*ppHidQueueInterface)->Release(ppHidQueueInterface); MY_CHECK_CREFS(cRefs);
844 }
845
846 /* Release the removal hook? */
847 /** @todo */
848
849 /* Close and release the device interface. */
850 if (pKeyboardEntry->ppHidDeviceInterface)
851 {
852 IOHIDDeviceInterface **ppHidDeviceInterface = pKeyboardEntry->ppHidDeviceInterface;
853 pKeyboardEntry->ppHidDeviceInterface = NULL;
854
855 (*ppHidDeviceInterface)->close(ppHidDeviceInterface);
856 cRefs = (*ppHidDeviceInterface)->Release(ppHidDeviceInterface); MY_CHECK_CREFS(cRefs);
857 }
858}
859
860/** Zap the keyboard cache. */
861static void darwinHIDKeyboardCacheZap(void)
862{
863 /* Release the old cache data first. */
864 while (g_cKeyboards > 0)
865 {
866 unsigned i = --g_cKeyboards;
867 darwinHIDKeyboardCacheDestroyEntry(&g_aKeyboards[i]);
868 }
869}
870
871/** Updates the cached keyboard data.
872 * @todo The current implementation is very brute force...
873 * Rewrite it so that it doesn't flush the cache completely but simply checks whether
874 * anything has changed in the HID config. With any luck, there might even be a callback
875 * or something we can poll for HID config changes...
876 * setRemovalCallback() is a start... */
877static void darwinHIDKeyboardCacheDoUpdate(void)
878{
879 g_u64KeyboardTS = RTTimeMilliTS();
880
881 /* Dispense with the old cache data. */
882 darwinHIDKeyboardCacheZap();
883
884 /* Open the master port on the first invocation. */
885 if (!g_MasterPort)
886 {
887 kern_return_t krc = IOMasterPort(MACH_PORT_NULL, &g_MasterPort);
888 AssertReturnVoid(krc == KERN_SUCCESS);
889 }
890
891 /* Create a matching dictionary for searching for keyboards devices. */
892 static const UInt32 s_Page = kHIDPage_GenericDesktop;
893 static const UInt32 s_Usage = kHIDUsage_GD_Keyboard;
894 CFMutableDictionaryRef RefMatchingDict = IOServiceMatching(kIOHIDDeviceKey);
895 AssertReturnVoid(RefMatchingDict);
896 CFDictionarySetValue(RefMatchingDict, CFSTR(kIOHIDPrimaryUsagePageKey),
897 CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &s_Page));
898 CFDictionarySetValue(RefMatchingDict, CFSTR(kIOHIDPrimaryUsageKey),
899 CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &s_Usage));
900
901 /* Perform the search and get a collection of keyboard devices. */
902 io_iterator_t Keyboards = NULL;
903 IOReturn rc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &Keyboards);
904 AssertMsgReturnVoid(rc == kIOReturnSuccess, ("rc=%d\n", rc));
905 RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */
906
907 /* Enumerate the keyboards and query the cache data. */
908 unsigned i = 0;
909 io_object_t KeyboardDevice;
910 while ( i < RT_ELEMENTS(g_aKeyboards)
911 && (KeyboardDevice = IOIteratorNext(Keyboards)) != 0)
912 {
913 if (darwinHIDKeyboardCacheCreateEntry(&g_aKeyboards[i], KeyboardDevice))
914 i++;
915 IOObjectRelease(KeyboardDevice);
916 }
917 g_cKeyboards = i;
918
919 IOObjectRelease(Keyboards);
920}
921
922/** Updates the keyboard cache if it's time to do it again. */
923static void darwinHIDKeyboardCacheUpdate(void)
924{
925 if ( !g_cKeyboards
926 /*|| g_u64KeyboardTS - RTTimeMilliTS() > 7500*/ /* 7.5sec */)
927 darwinHIDKeyboardCacheDoUpdate();
928}
929
930/** Queries the modifier keys from the (IOKit) HID Manager. */
931static UInt32 darwinQueryHIDModifiers(void)
932{
933 /* Iterate thru the keyboards collecting their modifier masks. */
934 UInt32 fHIDModifiers = 0;
935 unsigned i = g_cKeyboards;
936 while (i-- > 0)
937 {
938 IOHIDDeviceInterface **ppHidDeviceInterface = g_aKeyboards[i].ppHidDeviceInterface;
939 if (!ppHidDeviceInterface)
940 continue;
941
942 unsigned j = g_aKeyboards[i].cCookies;
943 while (j-- > 0)
944 {
945 IOHIDEventStruct HidEvent;
946 IOReturn rc = (*ppHidDeviceInterface)->getElementValue(ppHidDeviceInterface,
947 g_aKeyboards[i].aCookies[j].Cookie,
948 &HidEvent);
949 if (rc == kIOReturnSuccess)
950 {
951 if (HidEvent.value)
952 fHIDModifiers |= g_aKeyboards[i].aCookies[j].fMask;
953 }
954 else
955 AssertMsgFailed(("rc=%#x\n", rc));
956 }
957 }
958
959 return fHIDModifiers;
960}
961
962#endif /* USE_HID_FOR_MODIFIERS */
963
964
965void DarwinGrabKeyboard(bool fGlobalHotkeys)
966{
967 LogFlow(("DarwinGrabKeyboard: fGlobalHotkeys=%RTbool\n", fGlobalHotkeys));
968
969#ifdef USE_HID_FOR_MODIFIERS
970 /* Update the keyboard cache. */
971 darwinHIDKeyboardCacheUpdate();
972
973 /* Start the keyboard queues and query the current mask. */
974 g_fHIDQueueEnabled = true;
975
976 unsigned i = g_cKeyboards;
977 while (i-- > 0)
978 {
979 if (g_aKeyboards[i].ppHidQueueInterface)
980 (*g_aKeyboards[i].ppHidQueueInterface)->start(g_aKeyboards[i].ppHidQueueInterface);
981 }
982
983 g_fHIDModifierMask = darwinQueryHIDModifiers();
984#endif /* USE_HID_FOR_MODIFIERS */
985
986 /* Disable hotkeys if requested. */
987 if (fGlobalHotkeys)
988 DarwinDisableGlobalHotKeys(true);
989}
990
991void DarwinReleaseKeyboard()
992{
993 LogFlow(("DarwinReleaseKeyboard\n"));
994
995 /* Re-enable hotkeys. */
996 DarwinDisableGlobalHotKeys(false);
997
998#ifdef USE_HID_FOR_MODIFIERS
999 /* Stop and drain the keyboard queues. */
1000 g_fHIDQueueEnabled = false;
1001
1002#if 0
1003 unsigned i = g_cKeyboards;
1004 while (i-- > 0)
1005 {
1006 if (g_aKeyboards[i].ppHidQueueInterface)
1007 {
1008
1009 (*g_aKeyboards[i].ppHidQueueInterface)->stop(g_aKeyboards[i].ppHidQueueInterface);
1010
1011 /* drain it */
1012 IOReturn rc;
1013 unsigned c = 0;
1014 do
1015 {
1016 IOHIDEventStruct Event;
1017 AbsoluteTime MaxTime = {0,0};
1018 rc = (*g_aKeyboards[i].ppHidQueueInterface)->getNextEvent(g_aKeyboards[i].ppHidQueueInterface,
1019 &Event, MaxTime, 0);
1020 } while ( rc == kIOReturnSuccess
1021 && c++ < 32);
1022 }
1023 }
1024#else
1025 /* Kill the keyboard cache. */
1026 darwinHIDKeyboardCacheZap();
1027#endif
1028
1029 /* Clear the modifier mask. */
1030 g_fHIDModifierMask = 0;
1031#endif /* USE_HID_FOR_MODIFIERS */
1032}
1033
1034
1035#ifdef VBOX_WITH_KBD_LEDS_SYNC
1036
1037/** Prepares dictionary that will be used to match HID LED device(s) while discovering. */
1038static CFDictionaryRef darwinQueryLedDeviceMatchingDictionary()
1039{
1040 CFDictionaryRef deviceMatchingDictRef;
1041
1042 // Use two (key, value) pairs:
1043 // - (kIOHIDDeviceUsagePageKey, kHIDPage_GenericDesktop),
1044 // - (kIOHIDDeviceUsageKey, kHIDUsage_GD_Keyboard). */
1045
1046 CFNumberRef usagePageKeyCFNumberRef; int usagePageKeyCFNumberValue = kHIDPage_GenericDesktop;
1047 CFNumberRef usageKeyCFNumberRef; int usageKeyCFNumberValue = kHIDUsage_GD_Keyboard;
1048
1049 usagePageKeyCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePageKeyCFNumberValue);
1050 if (usagePageKeyCFNumberRef)
1051 {
1052 usageKeyCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usageKeyCFNumberValue);
1053 if (usageKeyCFNumberRef)
1054 {
1055 CFStringRef dictionaryKeys[2] = { CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey) };
1056 CFNumberRef dictionaryVals[2] = { usagePageKeyCFNumberRef, usageKeyCFNumberRef };
1057
1058 deviceMatchingDictRef = CFDictionaryCreate(kCFAllocatorDefault,
1059 (const void **)dictionaryKeys,
1060 (const void **)dictionaryVals,
1061 2, /** two (key, value) pairs */
1062 &kCFTypeDictionaryKeyCallBacks,
1063 &kCFTypeDictionaryValueCallBacks);
1064
1065 if (deviceMatchingDictRef)
1066 {
1067 CFRelease(usageKeyCFNumberRef);
1068 CFRelease(usagePageKeyCFNumberRef);
1069
1070 return deviceMatchingDictRef;
1071 }
1072
1073 CFRelease(usageKeyCFNumberRef);
1074 }
1075
1076 CFRelease(usagePageKeyCFNumberRef);
1077 }
1078
1079 return NULL;
1080}
1081
1082/** Prepare dictionary that will be used to match HID LED device element(s) while discovering. */
1083static CFDictionaryRef darwinQueryLedElementMatchingDictionary()
1084{
1085 CFDictionaryRef elementMatchingDictRef;
1086
1087 // Use only one (key, value) pair to match LED device element:
1088 // - (kIOHIDElementUsagePageKey, kHIDPage_LEDs). */
1089
1090 CFNumberRef usagePageKeyCFNumberRef; int usagePageKeyCFNumberValue = kHIDPage_LEDs;
1091
1092 usagePageKeyCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePageKeyCFNumberValue);
1093 if (usagePageKeyCFNumberRef)
1094 {
1095 CFStringRef dictionaryKeys[1] = { CFSTR(kIOHIDElementUsagePageKey), };
1096 CFNumberRef dictionaryVals[1] = { usagePageKeyCFNumberRef, };
1097
1098 elementMatchingDictRef = CFDictionaryCreate(kCFAllocatorDefault,
1099 (const void **)dictionaryKeys,
1100 (const void **)dictionaryVals,
1101 1, /** one (key, value) pair */
1102 &kCFTypeDictionaryKeyCallBacks,
1103 &kCFTypeDictionaryValueCallBacks);
1104
1105 if (elementMatchingDictRef)
1106 {
1107 CFRelease(usagePageKeyCFNumberRef);
1108 return elementMatchingDictRef;
1109 }
1110
1111 CFRelease(usagePageKeyCFNumberRef);
1112 }
1113
1114 return NULL;
1115}
1116
1117/** Turn ON or OFF a particular LED. */
1118static int darwinLedElementSetValue(IOHIDDeviceRef hidDevice, IOHIDElementRef element, bool fEnabled)
1119{
1120 IOHIDValueRef valueRef;
1121 IOReturn rc = kIOReturnError;
1122
1123 /* Try to resume suspended keyboard devices. Abort if failed in order to avoid GUI freezes. */
1124 int rc1 = SUPR3ResumeSuspendedKeyboards();
1125 if (RT_FAILURE(rc1))
1126 return rc1;
1127
1128 valueRef = IOHIDValueCreateWithIntegerValue(kCFAllocatorDefault, element, 0, (fEnabled) ? 1 : 0);
1129 if (valueRef)
1130 {
1131 rc = IOHIDDeviceSetValue(hidDevice, element, valueRef);
1132 if (rc != kIOReturnSuccess)
1133 LogRel2(("Warning! Something went wrong in attempt to turn %s HID device led (error %d)!\n", ((fEnabled) ? "on" : "off"), rc));
1134 else
1135 LogRel2(("Led (%d) is turned %s\n", (int)IOHIDElementGetUsage(element), ((fEnabled) ? "on" : "off")));
1136
1137 CFRelease(valueRef);
1138 }
1139
1140 return rc;
1141}
1142
1143/** Get state of a particular led. */
1144static int darwinLedElementGetValue(IOHIDDeviceRef hidDevice, IOHIDElementRef element, bool *fEnabled)
1145{
1146 /* Try to resume suspended keyboard devices. Abort if failed in order to avoid GUI freezes. */
1147 int rc1 = SUPR3ResumeSuspendedKeyboards();
1148 if (RT_FAILURE(rc1))
1149 return rc1;
1150
1151 IOHIDValueRef valueRef;
1152 IOReturn rc = IOHIDDeviceGetValue(hidDevice, element, &valueRef);
1153 if (rc == kIOReturnSuccess)
1154 {
1155 CFIndex integerValue = IOHIDValueGetIntegerValue(valueRef);
1156 switch (integerValue)
1157 {
1158 case 0:
1159 *fEnabled = false;
1160 break;
1161 case 1:
1162 *fEnabled = true;
1163 break;
1164 default:
1165 rc = kIOReturnError;
1166 }
1167
1168 /*CFRelease(valueRef); - IOHIDDeviceGetValue does not return a reference, so no need to release it. */
1169 }
1170
1171 return rc;
1172}
1173
1174/** Set corresponding states from NumLock, CapsLock and ScrollLock leds. */
1175static int darwinSetDeviceLedsState(IOHIDDeviceRef hidDevice, CFDictionaryRef elementMatchingDict,
1176 bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn)
1177{
1178 CFArrayRef matchingElementsArrayRef;
1179 int rc2 = 0;
1180
1181 matchingElementsArrayRef = IOHIDDeviceCopyMatchingElements(hidDevice, elementMatchingDict, kIOHIDOptionsTypeNone);
1182 if (matchingElementsArrayRef)
1183 {
1184 CFIndex cElements = CFArrayGetCount(matchingElementsArrayRef);
1185
1186 /* Cycle though all the elements we found */
1187 for (CFIndex i = 0; i < cElements; i++)
1188 {
1189 IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(matchingElementsArrayRef, i);
1190 uint32_t usage = IOHIDElementGetUsage(element);
1191 int rc = 0;
1192
1193 switch (usage)
1194 {
1195 case kHIDUsage_LED_NumLock:
1196 rc = darwinLedElementSetValue(hidDevice, element, fNumLockOn);
1197 break;
1198
1199 case kHIDUsage_LED_CapsLock:
1200 rc = darwinLedElementSetValue(hidDevice, element, fCapsLockOn);
1201 break;
1202 case kHIDUsage_LED_ScrollLock:
1203 rc = darwinLedElementSetValue(hidDevice, element, fScrollLockOn);
1204 break;
1205 }
1206 if (rc != 0)
1207 {
1208 LogRel2(("Failed to set led (%d) state\n", (int)IOHIDElementGetUsage(element)));
1209 rc2 = kIOReturnError;
1210 }
1211 }
1212
1213 CFRelease(matchingElementsArrayRef);
1214 }
1215
1216 return rc2;
1217}
1218
1219/** Get corresponding states for NumLock, CapsLock and ScrollLock leds. */
1220static int darwinGetDeviceLedsState(IOHIDDeviceRef hidDevice, CFDictionaryRef elementMatchingDict,
1221 bool *fNumLockOn, bool *fCapsLockOn, bool *fScrollLockOn)
1222{
1223 CFArrayRef matchingElementsArrayRef;
1224 int rc2 = 0;
1225
1226 matchingElementsArrayRef = IOHIDDeviceCopyMatchingElements(hidDevice, elementMatchingDict, kIOHIDOptionsTypeNone);
1227 if (matchingElementsArrayRef)
1228 {
1229 CFIndex cElements = CFArrayGetCount(matchingElementsArrayRef);
1230
1231 /* Cycle though all the elements we found */
1232 for (CFIndex i = 0; i < cElements; i++)
1233 {
1234 IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(matchingElementsArrayRef, i);
1235 uint32_t usage = IOHIDElementGetUsage(element);
1236 int rc = 0;
1237
1238 switch (usage)
1239 {
1240 case kHIDUsage_LED_NumLock:
1241 rc = darwinLedElementGetValue(hidDevice, element, fNumLockOn);
1242 break;
1243
1244 case kHIDUsage_LED_CapsLock:
1245 rc = darwinLedElementGetValue(hidDevice, element, fCapsLockOn);
1246 break;
1247 case kHIDUsage_LED_ScrollLock:
1248 rc = darwinLedElementGetValue(hidDevice, element, fScrollLockOn);
1249 break;
1250 }
1251 if (rc != 0)
1252 {
1253 LogRel2(("Failed to get led (%d) state\n", (int)IOHIDElementGetUsage(element)));
1254 rc2 = kIOReturnError;
1255 }
1256 }
1257
1258 CFRelease(matchingElementsArrayRef);
1259 }
1260
1261 return rc2;
1262}
1263
1264/** Get integer property of HID device */
1265static uint32_t darwinQueryIntProperty(IOHIDDeviceRef pHidDeviceRef, CFStringRef pProperty)
1266{
1267 CFTypeRef pNumberRef;
1268 uint32_t value = 0;
1269
1270 AssertReturn(pHidDeviceRef, 0);
1271 AssertReturn(pProperty, 0);
1272
1273 pNumberRef = IOHIDDeviceGetProperty(pHidDeviceRef, pProperty);
1274 if (pNumberRef)
1275 {
1276 if (CFGetTypeID(pNumberRef) == CFNumberGetTypeID())
1277 {
1278 if (CFNumberGetValue((CFNumberRef)pNumberRef, kCFNumberSInt32Type, &value))
1279 return value;
1280 }
1281 }
1282
1283 return 0;
1284}
1285
1286/** Get HID Vendor ID */
1287static uint32_t darwinHidVendorId(IOHIDDeviceRef pHidDeviceRef)
1288{
1289 return darwinQueryIntProperty(pHidDeviceRef, CFSTR(kIOHIDVendorIDKey));
1290}
1291
1292/** Get HID Product ID */
1293static uint32_t darwinHidProductId(IOHIDDeviceRef pHidDeviceRef)
1294{
1295 return darwinQueryIntProperty(pHidDeviceRef, CFSTR(kIOHIDProductIDKey));
1296}
1297
1298/** Get HID Location ID */
1299static uint32_t darwinHidLocationId(IOHIDDeviceRef pHidDeviceRef)
1300{
1301 return darwinQueryIntProperty(pHidDeviceRef, CFSTR(kIOHIDLocationIDKey));
1302}
1303
1304/** Some keyboard devices might freeze after LEDs manipulation. We filter out such devices here.
1305 * In the list below, devices that known to have such issues. If you want to add new device,
1306 * then add it here. Currently, we only filter devices by Vendor ID.
1307 * In future it might make sense to take Product ID into account as well. */
1308static bool darwinHidDeviceSupported(IOHIDDeviceRef pHidDeviceRef)
1309{
1310#ifndef VBOX_WITHOUT_KBD_LEDS_SYNC_FILTERING
1311 bool fSupported = true;
1312 uint32_t vendorId = darwinHidVendorId(pHidDeviceRef);
1313 uint32_t productId = darwinHidProductId(pHidDeviceRef);
1314
1315 if (vendorId == 0x05D5) /* Genius */
1316 {
1317 if (productId == 0x8001) /* GK-04008/C keyboard */
1318 fSupported = false;
1319 }
1320 if (vendorId == 0xE6A) /* Megawin Technology */
1321 {
1322 if (productId == 0x6001) /* Japanese flexible keyboard */
1323 fSupported = false;
1324 }
1325
1326 LogRel2(("HID device [VendorID=0x%X, ProductId=0x%X] %s in the list of supported devices.\n", vendorId, productId, (fSupported ? "is" : "is not")));
1327
1328 return fSupported;
1329#else /* !VBOX_WITH_KBD_LEDS_SYNC_FILTERING */
1330 return true;
1331#endif
1332}
1333
1334/** IOKit key press callback helper: take care about key-down event.
1335 * This code should be executed within a critical section under pHidState->fifoEventQueueLock. */
1336static void darwinHidInputCbKeyDown(VBoxKbdState_t *pKbd, uint32_t iKeyCode, VBoxHidsState_t *pHidState)
1337{
1338 VBoxKbdEvent_t *pEvent = (VBoxKbdEvent_t *)malloc(sizeof(VBoxKbdEvent_t));
1339
1340 if (pEvent)
1341 {
1342 /* Queue Key-Down event. */
1343 pEvent->tsKeyDown = RTTimeSystemMilliTS();
1344 pEvent->pKbd = pKbd;
1345 pEvent->iKeyCode = iKeyCode;
1346
1347 CFArrayAppendValue(pHidState->pFifoEventQueue, (void *)pEvent);
1348
1349 LogRel2(("IOHID: KBD %d: Modifier Key-Down event\n", (int)pKbd->idxPosition));
1350 }
1351 else
1352 LogRel2(("IOHID: Modifier Key-Up event. Unable to find memory for KBD %d event\n", (int)pKbd->idxPosition));
1353}
1354
1355/** IOkit and Carbon key press callbacks helper: CapsLock timeout checker.
1356 *
1357 * Returns FALSE if CAPS LOCK timeout not occurred and its state still was not switched (Apple kbd).
1358 * Returns TRUE if CAPS LOCK timeout occurred and its state was switched (Apple kbd).
1359 * Returns TRUE for non-Apple kbd. */
1360static bool darwinKbdCapsEventMatches(VBoxKbdEvent_t *pEvent, bool fCapsLed)
1361{
1362 // CapsLock timeout is only applicable if conditions
1363 // below are satisfied:
1364 //
1365 // a) Key pressed on Apple keyboard
1366 // b) CapsLed is OFF at the moment when CapsLock key is pressed
1367
1368 bool fAppleKeyboard = (pEvent->pKbd->cCapsLockTimeout > 0);
1369
1370 /* Apple keyboard */
1371 if (fAppleKeyboard && !fCapsLed)
1372 {
1373 uint64_t tsDiff = RTTimeSystemMilliTS() - pEvent->tsKeyDown;
1374 if (tsDiff < pEvent->pKbd->cCapsLockTimeout)
1375 return false;
1376 }
1377
1378 return true;
1379}
1380
1381/** IOKit key press callback helper: take care about key-up event.
1382 * This code should be executed within a critical section under pHidState->fifoEventQueueLock. */
1383static void darwinHidInputCbKeyUp(VBoxKbdState_t *pKbd, uint32_t iKeyCode, VBoxHidsState_t *pHidState)
1384{
1385 CFIndex iQueue = 0;
1386 VBoxKbdEvent_t *pEvent = NULL;
1387
1388 // Key-up event assumes that key-down event occured previously. If so, an event
1389 // data should be in event queue. Attempt to find it.
1390 for (CFIndex i = 0; i < CFArrayGetCount(pHidState->pFifoEventQueue); i++)
1391 {
1392 VBoxKbdEvent_t *pCachedEvent = (VBoxKbdEvent_t *)CFArrayGetValueAtIndex(pHidState->pFifoEventQueue, i);
1393
1394 if (pCachedEvent && pCachedEvent->pKbd == pKbd && pCachedEvent->iKeyCode == iKeyCode)
1395 {
1396 pEvent = pCachedEvent;
1397 iQueue = i;
1398 break;
1399 }
1400 }
1401
1402 /* Event found. */
1403 if (pEvent)
1404 {
1405 // NUM LOCK should not have timeout and its press should immidiately trigger Carbon callback.
1406 // Therefore, if it is still in queue this is a problem because it was not handled by Carbon callback.
1407 // This mean that NUM LOCK is most likely out of sync.
1408 if (iKeyCode == kHIDUsage_KeypadNumLock)
1409 {
1410 LogRel2(("IOHID: KBD %d: Modifier Key-Up event. Key-Down event was not habdled by Carbon callback. "
1411 "NUM LOCK is most likely out of sync\n", (int)pKbd->idxPosition));
1412 }
1413 else if (iKeyCode == kHIDUsage_KeyboardCapsLock)
1414 {
1415 // If CAPS LOCK key-press event still not match CAPS LOCK timeout criteria, Carbon callback
1416 // should not be triggered for this event at all. Threfore, event should be removed from queue.
1417 if (!darwinKbdCapsEventMatches(pEvent, pHidState->guestState.fCapsLockOn))
1418 {
1419 CFArrayRemoveValueAtIndex(pHidState->pFifoEventQueue, iQueue);
1420
1421 LogRel2(("IOHID: KBD %d: Modifier Key-Up event on Apple keyboard. Key-Down event was triggered %llu ms "
1422 "ago. Carbon event should not be triggered, removed from queue\n", (int)pKbd->idxPosition,
1423 RTTimeSystemMilliTS() - pEvent->tsKeyDown));
1424 free(pEvent);
1425 }
1426 else
1427 {
1428 // CAPS LOCK key-press event matches to CAPS LOCK timeout criteria and still present in queue.
1429 // This might mean that Carbon callback was triggered for this event, but cached keyboard state was not updated.
1430 // It also might mean that Carbon callback still was not triggered, but it will be soon.
1431 // Threfore, CAPS LOCK might be out of sync.
1432 LogRel2(("IOHID: KBD %d: Modifier Key-Up event. Key-Down event was triggered %llu ms "
1433 "ago and still was not handled by Carbon callback. CAPS LOCK might out of sync if "
1434 "Carbon will not handle this\n", (int)pKbd->idxPosition, RTTimeSystemMilliTS() - pEvent->tsKeyDown));
1435 }
1436 }
1437 }
1438 else
1439 LogRel2(("IOHID: KBD %d: Modifier Key-Up event. Modifier state change was "
1440 "successfully handled by Carbon callback\n", (int)pKbd->idxPosition));
1441}
1442
1443/** IOKit key press callback. Triggered before Carbon callback. We remember which keyboard produced a keypress here. */
1444static void darwinHidInputCallback(void *pData, IOReturn unused, void *unused1, IOHIDValueRef pValueRef)
1445{
1446 (void)unused;
1447 (void)unused1;
1448
1449 AssertReturnVoid(pValueRef);
1450
1451 IOHIDElementRef pElementRef = IOHIDValueGetElement(pValueRef);
1452 AssertReturnVoid(pElementRef);
1453
1454 uint32_t usage = IOHIDElementGetUsage(pElementRef);
1455
1456 if (IOHIDElementGetUsagePage(pElementRef) == kHIDPage_KeyboardOrKeypad) /* Keyboard or keypad event */
1457 if (usage == kHIDUsage_KeyboardCapsLock || /* CapsLock key has been pressed */
1458 usage == kHIDUsage_KeypadNumLock) /* ... or NumLock key has been pressed */
1459 {
1460 VBoxKbdState_t *pKbd = (VBoxKbdState_t *)pData;
1461
1462 if (pKbd && pKbd->pParentContainer)
1463 {
1464 bool fKeyDown = (IOHIDValueGetIntegerValue(pValueRef) == 1);
1465 VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pKbd->pParentContainer;
1466
1467 AssertReturnVoid(pHidState);
1468
1469 if (RT_FAILURE(RTSemMutexRequest(pHidState->fifoEventQueueLock, RT_INDEFINITE_WAIT)))
1470 return ;
1471
1472 /* Handle corresponding event. */
1473 if (fKeyDown)
1474 darwinHidInputCbKeyDown(pKbd, usage, pHidState);
1475 else
1476 darwinHidInputCbKeyUp(pKbd, usage, pHidState);
1477
1478 RTSemMutexRelease(pHidState->fifoEventQueueLock);
1479 }
1480 else
1481 LogRel2(("IOHID: No KBD: A modifier key has been pressed\n"));
1482 }
1483}
1484
1485/** Carbon key press callback helper: find last occured KBD event in queue
1486 * (ignoring those events which do not match CAPS LOCK timeout criteria).
1487 * Once event found, it is removed from queue. This code should be executed
1488 * within a critical section under pHidState->fifoEventQueueLock. */
1489static VBoxKbdEvent_t *darwinCarbonCbFindEvent(VBoxHidsState_t *pHidState)
1490{
1491 VBoxKbdEvent_t *pEvent = NULL;
1492
1493 for (CFIndex i = 0; i < CFArrayGetCount(pHidState->pFifoEventQueue); i++)
1494 {
1495 pEvent = (VBoxKbdEvent_t *)CFArrayGetValueAtIndex(pHidState->pFifoEventQueue, i);
1496
1497 /* Paranoia: skip potentially dangerous data items. */
1498 if (!pEvent || !pEvent->pKbd) continue;
1499
1500 if ( pEvent->iKeyCode == kHIDUsage_KeypadNumLock
1501 || (pEvent->iKeyCode == kHIDUsage_KeyboardCapsLock && darwinKbdCapsEventMatches(pEvent, pHidState->guestState.fCapsLockOn)))
1502 {
1503 /* Found one. Remove it from queue. */
1504 CFArrayRemoveValueAtIndex(pHidState->pFifoEventQueue, i);
1505
1506 LogRel2(("CARBON: Found event in queue: %d (KBD %d, tsKeyDown=%llu, pressed %llu ms ago)\n", (int)i,
1507 (int)pEvent->pKbd->idxPosition, pEvent->tsKeyDown, RTTimeSystemMilliTS() - pEvent->tsKeyDown));
1508
1509 break;
1510 }
1511 else
1512 LogRel2(("CARBON: Skip keyboard event from KBD %d, key pressed %llu ms ago\n",
1513 (int)pEvent->pKbd->idxPosition, RTTimeSystemMilliTS() - pEvent->tsKeyDown));
1514
1515 pEvent = NULL;
1516 }
1517
1518 return pEvent;
1519}
1520
1521/** Carbon key press callback. Triggered after IOKit callback. */
1522static CGEventRef darwinCarbonCallback(CGEventTapProxy unused, CGEventType unused1, CGEventRef pEventRef, void *pData)
1523{
1524 (void)unused;
1525 (void)unused1;
1526
1527 CGEventFlags fMask = CGEventGetFlags(pEventRef);
1528 bool fCaps = (bool)(fMask & NX_ALPHASHIFTMASK);
1529 bool fNum = (bool)(fMask & NX_NUMERICPADMASK);
1530 CGKeyCode key = CGEventGetIntegerValueField(pEventRef, kCGKeyboardEventKeycode);
1531
1532 VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pData;
1533 AssertReturn(pHidState, pEventRef);
1534
1535 if (RT_FAILURE(RTSemMutexRequest(pHidState->fifoEventQueueLock, RT_INDEFINITE_WAIT)))
1536 return pEventRef;
1537
1538 if (key == kHIDUsage_KeyboardCapsLock ||
1539 key == kHIDUsage_KeypadNumLock)
1540 {
1541 /* Attempt to find an event queued by IOKit callback. */
1542 VBoxKbdEvent_t *pEvent = darwinCarbonCbFindEvent(pHidState);
1543 if (pEvent)
1544 {
1545 VBoxKbdState_t *pKbd = pEvent->pKbd;
1546
1547 LogRel2(("CARBON: KBD %d: caps=%s, num=%s. tsKeyDown=%llu, tsKeyUp=%llu [tsDiff=%llu ms]. %d events in queue.\n",
1548 (int)pKbd->idxPosition, VBOX_BOOL_TO_STR_STATE(fCaps), VBOX_BOOL_TO_STR_STATE(fNum),
1549 pEvent->tsKeyDown, RTTimeSystemMilliTS(), RTTimeSystemMilliTS() - pEvent->tsKeyDown,
1550 CFArrayGetCount(pHidState->pFifoEventQueue)));
1551
1552 pKbd->LED.fCapsLockOn = fCaps;
1553 pKbd->LED.fNumLockOn = fNum;
1554
1555 /* Silently resync last touched KBD device */
1556 if (pHidState)
1557 {
1558 CFDictionaryRef elementMatchingDict = darwinQueryLedElementMatchingDictionary();
1559 if (elementMatchingDict)
1560 {
1561 (void)darwinSetDeviceLedsState(pKbd->pDevice,
1562 elementMatchingDict,
1563 pHidState->guestState.fNumLockOn,
1564 pHidState->guestState.fCapsLockOn,
1565 pHidState->guestState.fScrollLockOn);
1566
1567 CFRelease(elementMatchingDict);
1568 }
1569 }
1570
1571 free(pEvent);
1572 }
1573 else
1574 LogRel2(("CARBON: No KBD to take care when modifier key has been pressed: caps=%s, num=%s (%d events in queue)\n",
1575 VBOX_BOOL_TO_STR_STATE(fCaps), VBOX_BOOL_TO_STR_STATE(fNum), CFArrayGetCount(pHidState->pFifoEventQueue)));
1576 }
1577
1578 RTSemMutexRelease(pHidState->fifoEventQueueLock);
1579
1580 return pEventRef;
1581}
1582
1583/** Helper function to obtain interface for IOUSBInterface IOService. */
1584static IOUSBDeviceInterface ** darwinQueryUsbHidInterfaceInterface(io_service_t service)
1585{
1586 kern_return_t rc;
1587 IOCFPlugInInterface **ppPluginInterface = NULL;
1588 SInt32 iScore;
1589
1590 rc = IOCreatePlugInInterfaceForService(service, kIOUSBInterfaceUserClientTypeID,
1591 kIOCFPlugInInterfaceID, &ppPluginInterface, &iScore);
1592
1593 if (rc == kIOReturnSuccess && ppPluginInterface != NULL)
1594 {
1595 IOUSBDeviceInterface **ppUsbDeviceInterface = NULL;
1596
1597 rc = (*ppPluginInterface)->QueryInterface(ppPluginInterface, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
1598 (LPVOID *)&ppUsbDeviceInterface);
1599 IODestroyPlugInInterface(ppPluginInterface);
1600
1601 if (rc == kIOReturnSuccess && ppUsbDeviceInterface != NULL)
1602 return ppUsbDeviceInterface;
1603 else
1604 LogRel2(("Failed to query plugin interface for USB device\n"));
1605
1606 }
1607 else
1608 LogRel2(("Failed to create plugin interface for USB device\n"));
1609
1610 return NULL;
1611}
1612
1613/** Helper function for IOUSBInterface IOService general interest notification callback: resync LEDs. */
1614static void darwinUsbHidResyncLeds(VBoxKbdState_t *pKbd)
1615{
1616 AssertReturnVoid(pKbd);
1617
1618 VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pKbd->pParentContainer;
1619 CFDictionaryRef elementMatchingDict = darwinQueryLedElementMatchingDictionary();
1620 if (elementMatchingDict)
1621 {
1622 LogRel2(("Do HID device resync at location 0x%X \n", pKbd->idLocation));
1623 (void)darwinSetDeviceLedsState(pKbd->pDevice, elementMatchingDict,
1624 pHidState->guestState.fNumLockOn, pHidState->guestState.fCapsLockOn, pHidState->guestState.fScrollLockOn);
1625 CFRelease(elementMatchingDict);
1626 }
1627}
1628
1629/** IOUSBInterface IOService general interest notification callback. When we receive it, we do
1630 * silently resync kbd which has just changed its state. */
1631static void darwinUsbHidGeneralInterestCb(void *pData, io_service_t unused1, natural_t msg, void *unused2)
1632{
1633 NOREF(unused1);
1634 NOREF(unused2);
1635
1636 AssertReturnVoid(pData);
1637 VBoxKbdState_t *pKbd = (VBoxKbdState_t *)pData;
1638
1639 switch (msg)
1640 {
1641 case kIOUSBMessagePortHasBeenSuspended:
1642 {
1643 LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessagePortHasBeenSuspended for KBD %d (Location ID: 0x%X)\n",
1644 (int)(pKbd->idxPosition), pKbd->idLocation));
1645 break;
1646 }
1647
1648 case kIOUSBMessagePortHasBeenResumed:
1649 {
1650 LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessagePortHasBeenResumed for KBD %d (Location ID: 0x%X)\n",
1651 (int)(pKbd->idxPosition), pKbd->idLocation));
1652 break;
1653 }
1654
1655 case kIOUSBMessagePortHasBeenReset:
1656 {
1657 LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessagePortHasBeenReset for KBD %d (Location ID: 0x%X)\n",
1658 (int)(pKbd->idxPosition), pKbd->idLocation));
1659 darwinUsbHidResyncLeds(pKbd);
1660 break;
1661 }
1662
1663 case kIOUSBMessageCompositeDriverReconfigured:
1664 {
1665 LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessageCompositeDriverReconfigured for KBD %d (Location ID: 0x%X)\n",
1666 (int)(pKbd->idxPosition), pKbd->idLocation));
1667 break;
1668 }
1669
1670 case kIOMessageServiceWasClosed:
1671 {
1672 LogRel2(("IOUSBInterface IOService general interest notification kIOMessageServiceWasClosed for KBD %d (Location ID: 0x%X)\n",
1673 (int)(pKbd->idxPosition), pKbd->idLocation));
1674 break;
1675 }
1676
1677 default:
1678 LogRel2(("IOUSBInterface IOService general interest notification 0x%X for KBD %d (Location ID: 0x%X)\n",
1679 msg, (int)(pKbd->idxPosition), pKbd->idLocation));
1680 }
1681}
1682
1683/** Get pre-cached KBD device by its Location ID. */
1684static VBoxKbdState_t *darwinUsbHidQueryKbdByLocationId(uint32_t idLocation, VBoxHidsState_t *pHidState)
1685{
1686 AssertReturn(pHidState, NULL);
1687
1688 for (CFIndex i = 0; i < CFArrayGetCount(pHidState->pDeviceCollection); i++)
1689 {
1690 VBoxKbdState_t *pKbd = (VBoxKbdState_t *)CFArrayGetValueAtIndex(pHidState->pDeviceCollection, i);
1691 if (pKbd && pKbd->idLocation == idLocation)
1692 {
1693 LogRel2(("Lookup USB HID Device by location ID 0x%X: found match\n", idLocation));
1694 return pKbd;
1695 }
1696 }
1697
1698 LogRel2(("Lookup USB HID Device by location ID 0x%X: no matches found:\n", idLocation));
1699
1700 return NULL;
1701}
1702
1703/** IOUSBInterface IOService match notification callback: issued when IOService instantinates.
1704 * We subscribe to general interest notifications for available IOServices here. */
1705static void darwinUsbHidDeviceMatchCb(void *pData, io_iterator_t iter)
1706{
1707 AssertReturnVoid(pData);
1708
1709 io_service_t service;
1710 VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pData;
1711
1712 while ((service = IOIteratorNext(iter)))
1713 {
1714 kern_return_t rc;
1715
1716 IOUSBDeviceInterface **ppUsbDeviceInterface = darwinQueryUsbHidInterfaceInterface(service);
1717
1718 if (ppUsbDeviceInterface)
1719 {
1720 uint8_t idDeviceClass, idDeviceSubClass;
1721 UInt32 idLocation;
1722
1723 rc = (*ppUsbDeviceInterface)->GetLocationID (ppUsbDeviceInterface, &idLocation); AssertMsg(rc == 0, ("Failed to get Location ID"));
1724 rc = (*ppUsbDeviceInterface)->GetDeviceClass (ppUsbDeviceInterface, &idDeviceClass); AssertMsg(rc == 0, ("Failed to get Device Class"));
1725 rc = (*ppUsbDeviceInterface)->GetDeviceSubClass(ppUsbDeviceInterface, &idDeviceSubClass); AssertMsg(rc == 0, ("Failed to get Device Subclass"));
1726 RT_NOREF(rc);
1727
1728 if (idDeviceClass == kUSBHIDInterfaceClass && idDeviceSubClass == kUSBHIDBootInterfaceSubClass)
1729 {
1730 VBoxKbdState_t *pKbd = darwinUsbHidQueryKbdByLocationId((uint32_t)idLocation, pHidState);
1731
1732 if (pKbd)
1733 {
1734 rc = IOServiceAddInterestNotification(pHidState->pNotificationPrortRef, service, kIOGeneralInterest,
1735 darwinUsbHidGeneralInterestCb, pKbd, &pHidState->pUsbHidGeneralInterestNotify);
1736
1737 AssertMsg(rc == 0, ("Failed to add general interest notification"));
1738
1739 LogRel2(("Found HID device at location 0x%X: class 0x%X, subclass 0x%X\n", idLocation, idDeviceClass, idDeviceSubClass));
1740 }
1741 }
1742
1743 rc = (*ppUsbDeviceInterface)->Release(ppUsbDeviceInterface); AssertMsg(rc == 0, ("Failed to release USB device interface"));
1744 }
1745
1746 IOObjectRelease(service);
1747 }
1748}
1749
1750/** Register IOUSBInterface IOService match notification callback in order to recync KBD
1751 * device when it reports state change. */
1752static int darwinUsbHidSubscribeInterestNotifications(VBoxHidsState_t *pHidState)
1753{
1754 AssertReturn(pHidState, kIOReturnBadArgument);
1755
1756 int rc = kIOReturnNoMemory;
1757 CFDictionaryRef pDictionary = IOServiceMatching(kIOUSBInterfaceClassName);
1758
1759 if (pDictionary)
1760 {
1761 RT_GCC_NO_WARN_DEPRECATED_BEGIN
1762 pHidState->pNotificationPrortRef = IONotificationPortCreate(kIOMasterPortDefault); /* kIOMasterPortDefault: Deprecated since 12.0. */
1763 RT_GCC_NO_WARN_DEPRECATED_END
1764 if (pHidState->pNotificationPrortRef)
1765 {
1766 CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(pHidState->pNotificationPrortRef), kCFRunLoopDefaultMode);
1767
1768 rc = IOServiceAddMatchingNotification(pHidState->pNotificationPrortRef, kIOMatchedNotification,
1769 pDictionary, darwinUsbHidDeviceMatchCb, pHidState,
1770 &pHidState->pUsbHidDeviceMatchNotify);
1771
1772 if (rc == kIOReturnSuccess && pHidState->pUsbHidDeviceMatchNotify != IO_OBJECT_NULL)
1773 {
1774 darwinUsbHidDeviceMatchCb(pHidState, pHidState->pUsbHidDeviceMatchNotify);
1775 LogRel2(("Successfully subscribed to IOUSBInterface IOService match notifications\n"));
1776 }
1777 else
1778 LogRel2(("Failed to subscribe to IOUSBInterface IOService match notifications: subscription error 0x%X\n", rc));
1779 }
1780 else
1781 LogRel2(("Failed to subscribe to IOUSBInterface IOService match notifications: unable to create notification port\n"));
1782 }
1783 else
1784 LogRel2(("Failed to subscribe to IOUSBInterface IOService match notifications: no memory\n"));
1785
1786 return rc;
1787}
1788
1789/** Remove IOUSBInterface IOService match notification subscription. */
1790static void darwinUsbHidUnsubscribeInterestNotifications(VBoxHidsState_t *pHidState)
1791{
1792 AssertReturnVoid(pHidState);
1793
1794 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(pHidState->pNotificationPrortRef), kCFRunLoopDefaultMode);
1795 IONotificationPortDestroy(pHidState->pNotificationPrortRef);
1796 pHidState->pNotificationPrortRef = NULL;
1797
1798 LogRel2(("Successfully un-subscribed from IOUSBInterface IOService match notifications\n"));
1799}
1800
1801/** This callback is called when user physically removes HID device. We remove device from cache here. */
1802static void darwinHidRemovalCallback(void *pData, IOReturn unused, void *unused1)
1803{
1804 (void)unused;
1805 (void)unused1;
1806
1807 VBoxKbdState_t *pKbd = (VBoxKbdState_t *)pData; AssertReturnVoid(pKbd);
1808 VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pKbd->pParentContainer; AssertReturnVoid(pHidState);
1809
1810 AssertReturnVoid(pHidState->pDeviceCollection);
1811
1812 LogRel2(("Forget KBD %d\n", (int)pKbd->idxPosition));
1813
1814 //if (RT_FAILURE(RTSemMutexRequest(pHidState->fifoEventQueueLock, RT_INDEFINITE_WAIT)))
1815 // return ;
1816
1817 CFArrayRemoveValueAtIndex(pHidState->pDeviceCollection, pKbd->idxPosition);
1818 free(pKbd);
1819
1820 //RTSemMutexRelease(pHidState->fifoEventQueueLock);
1821}
1822
1823/** Check if we already cached given device */
1824static bool darwinIsDeviceInCache(VBoxHidsState_t *pState, IOHIDDeviceRef pDevice)
1825{
1826 AssertReturn(pState, false);
1827 AssertReturn(pState->pDeviceCollection, false);
1828
1829 for (CFIndex i = 0; i < CFArrayGetCount(pState->pDeviceCollection); i++)
1830 {
1831 VBoxKbdState_t *pKbd = (VBoxKbdState_t *)CFArrayGetValueAtIndex(pState->pDeviceCollection, i);
1832 if (pKbd && pKbd->pDevice == pDevice)
1833 return true;
1834 }
1835
1836 return false;
1837}
1838
1839/** Add device to cache. */
1840static void darwinHidAddDevice(VBoxHidsState_t *pHidState, IOHIDDeviceRef pDevice, bool fApplyLedState)
1841{
1842 int rc;
1843
1844 if (!darwinIsDeviceInCache(pHidState, pDevice))
1845 {
1846 if (IOHIDDeviceConformsTo(pDevice, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)
1847 && darwinHidDeviceSupported(pDevice))
1848 {
1849 VBoxKbdState_t *pKbd = (VBoxKbdState_t *)malloc(sizeof(VBoxKbdState_t));
1850 if (pKbd)
1851 {
1852 pKbd->pDevice = pDevice;
1853 pKbd->pParentContainer = (void *)pHidState;
1854 pKbd->idxPosition = CFArrayGetCount(pHidState->pDeviceCollection);
1855 pKbd->idLocation = darwinHidLocationId(pDevice);
1856
1857 // Some Apple keyboards have CAPS LOCK key timeout. According to corresponding
1858 // kext plist files, it is equals to 75 ms. For such devices we only add info into our FIFO event
1859 // queue if the time between Key-Down and Key-Up events >= 75 ms.
1860 pKbd->cCapsLockTimeout = (darwinHidVendorId(pKbd->pDevice) == kIOUSBVendorIDAppleComputer) ? 75 : 0;
1861
1862 CFDictionaryRef elementMatchingDict = darwinQueryLedElementMatchingDictionary();
1863 if (elementMatchingDict)
1864 {
1865 rc = darwinGetDeviceLedsState(pKbd->pDevice,
1866 elementMatchingDict,
1867 &pKbd->LED.fNumLockOn,
1868 &pKbd->LED.fCapsLockOn,
1869 &pKbd->LED.fScrollLockOn);
1870
1871 // This should never happen, but if happened -- mark all the leds of current
1872 // device as turned OFF.
1873 if (rc != 0)
1874 {
1875 LogRel2(("Unable to get leds state for device %d. Mark leds as turned off\n", (int)(pKbd->idxPosition)));
1876 pKbd->LED.fNumLockOn =
1877 pKbd->LED.fCapsLockOn =
1878 pKbd->LED.fScrollLockOn = false;
1879 }
1880
1881 /* Register per-device removal callback */
1882 IOHIDDeviceRegisterRemovalCallback(pKbd->pDevice, darwinHidRemovalCallback, (void *)pKbd);
1883
1884 /* Register per-device input callback */
1885 IOHIDDeviceRegisterInputValueCallback(pKbd->pDevice, darwinHidInputCallback, (void *)pKbd);
1886 IOHIDDeviceScheduleWithRunLoop(pKbd->pDevice, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
1887
1888 CFArrayAppendValue(pHidState->pDeviceCollection, (void *)pKbd);
1889
1890 LogRel2(("Saved LEDs for KBD %d (%p): fNumLockOn=%s, fCapsLockOn=%s, fScrollLockOn=%s\n",
1891 (int)pKbd->idxPosition, pKbd, VBOX_BOOL_TO_STR_STATE(pKbd->LED.fNumLockOn), VBOX_BOOL_TO_STR_STATE(pKbd->LED.fCapsLockOn),
1892 VBOX_BOOL_TO_STR_STATE(pKbd->LED.fScrollLockOn)));
1893
1894 if (fApplyLedState)
1895 {
1896 rc = darwinSetDeviceLedsState(pKbd->pDevice, elementMatchingDict, pHidState->guestState.fNumLockOn,
1897 pHidState->guestState.fCapsLockOn, pHidState->guestState.fScrollLockOn);
1898 if (rc != 0)
1899 LogRel2(("Unable to apply guest state to newly attached device\n"));
1900 }
1901
1902 CFRelease(elementMatchingDict);
1903 return;
1904 }
1905
1906 free(pKbd);
1907 }
1908 }
1909 }
1910}
1911
1912/** This callback is called when new HID device discovered by IOHIDManager. We add devices to cache here and only here! */
1913static void darwinHidMatchingCallback(void *pData, IOReturn unused, void *unused1, IOHIDDeviceRef pDevice)
1914{
1915 (void)unused;
1916 (void)unused1;
1917
1918 VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pData;
1919
1920 AssertReturnVoid(pHidState);
1921 AssertReturnVoid(pHidState->pDeviceCollection);
1922 AssertReturnVoid(pDevice);
1923
1924 darwinHidAddDevice(pHidState, pDevice, true);
1925}
1926
1927/** Register Carbon key press callback. */
1928static int darwinAddCarbonHandler(VBoxHidsState_t *pHidState)
1929{
1930 CFMachPortRef pTapRef;
1931 CGEventMask fMask = CGEventMaskBit(kCGEventFlagsChanged);
1932
1933 AssertReturn(pHidState, kIOReturnError);
1934
1935 /* Create FIFO event queue for keyboard events */
1936 pHidState->pFifoEventQueue = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
1937 AssertReturn(pHidState->pFifoEventQueue, kIOReturnError);
1938
1939 /* Create Lock for FIFO event queue */
1940 if (RT_FAILURE(RTSemMutexCreate(&pHidState->fifoEventQueueLock)))
1941 {
1942 LogRel2(("Unable to create Lock for FIFO event queue\n"));
1943 CFRelease(pHidState->pFifoEventQueue);
1944 pHidState->pFifoEventQueue = NULL;
1945 return kIOReturnError;
1946 }
1947
1948 pTapRef = CGEventTapCreate(kCGSessionEventTap, kCGTailAppendEventTap, kCGEventTapOptionDefault, fMask,
1949 darwinCarbonCallback, (void *)pHidState);
1950 if (pTapRef)
1951 {
1952 CFRunLoopSourceRef pLoopSourceRef;
1953 pLoopSourceRef = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, pTapRef, 0);
1954 if (pLoopSourceRef)
1955 {
1956 CFRunLoopAddSource(CFRunLoopGetCurrent(), pLoopSourceRef, kCFRunLoopDefaultMode);
1957 CGEventTapEnable(pTapRef, true);
1958
1959 pHidState->pTapRef = pTapRef;
1960 pHidState->pLoopSourceRef = pLoopSourceRef;
1961
1962 return 0;
1963 }
1964 else
1965 LogRel2(("Unable to create a loop source\n"));
1966
1967 CFRelease(pTapRef);
1968 }
1969 else
1970 LogRel2(("Unable to create an event tap\n"));
1971
1972 return kIOReturnError;
1973}
1974
1975/** Remove Carbon key press callback. */
1976static void darwinRemoveCarbonHandler(VBoxHidsState_t *pHidState)
1977{
1978 AssertReturnVoid(pHidState);
1979 AssertReturnVoid(pHidState->pTapRef);
1980 AssertReturnVoid(pHidState->pLoopSourceRef);
1981 AssertReturnVoid(pHidState->pFifoEventQueue);
1982
1983 CGEventTapEnable(pHidState->pTapRef, false);
1984 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), pHidState->pLoopSourceRef, kCFRunLoopDefaultMode);
1985 CFRelease(pHidState->pLoopSourceRef);
1986 CFRelease(pHidState->pTapRef);
1987
1988 RTSemMutexRequest(pHidState->fifoEventQueueLock, RT_INDEFINITE_WAIT);
1989 CFRelease(pHidState->pFifoEventQueue);
1990 pHidState->pFifoEventQueue = NULL;
1991 RTSemMutexRelease(pHidState->fifoEventQueueLock);
1992
1993 RTSemMutexDestroy(pHidState->fifoEventQueueLock);
1994}
1995
1996#endif /* !VBOX_WITH_KBD_LEDS_SYNC */
1997
1998
1999void *DarwinHidDevicesKeepLedsState()
2000{
2001#ifdef VBOX_WITH_KBD_LEDS_SYNC
2002 IOReturn rc;
2003 VBoxHidsState_t *pHidState;
2004
2005 pHidState = (VBoxHidsState_t *)malloc(sizeof(VBoxHidsState_t));
2006 AssertReturn(pHidState, NULL);
2007
2008 pHidState->hidManagerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
2009 if (pHidState->hidManagerRef)
2010 {
2011 CFDictionaryRef deviceMatchingDictRef = darwinQueryLedDeviceMatchingDictionary();
2012 if (deviceMatchingDictRef)
2013 {
2014 IOHIDManagerScheduleWithRunLoop(pHidState->hidManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
2015 IOHIDManagerSetDeviceMatching(pHidState->hidManagerRef, deviceMatchingDictRef);
2016
2017 rc = IOHIDManagerOpen(pHidState->hidManagerRef, kIOHIDOptionsTypeNone);
2018 if (rc == kIOReturnSuccess)
2019 {
2020 pHidState->pDeviceCollection = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
2021 if (pHidState->pDeviceCollection)
2022 {
2023 if (darwinAddCarbonHandler(pHidState) == 0)
2024 {
2025 /* Populate cache with HID devices */
2026 CFSetRef pDevicesSet = IOHIDManagerCopyDevices(pHidState->hidManagerRef);
2027 if (pDevicesSet)
2028 {
2029 CFIndex cDevices = CFSetGetCount(pDevicesSet);
2030
2031 IOHIDDeviceRef *ppDevices = (IOHIDDeviceRef *)malloc((size_t)cDevices * sizeof(IOHIDDeviceRef));
2032 if (ppDevices)
2033 {
2034 CFSetGetValues(pDevicesSet, (const void **)ppDevices);
2035 for (CFIndex i= 0; i < cDevices; i++)
2036 darwinHidAddDevice(pHidState, (IOHIDDeviceRef)ppDevices[i], false);
2037
2038 free(ppDevices);
2039 }
2040
2041 CFRelease(pDevicesSet);
2042 }
2043
2044 IOHIDManagerRegisterDeviceMatchingCallback(pHidState->hidManagerRef, darwinHidMatchingCallback, (void *)pHidState);
2045
2046 CFRelease(deviceMatchingDictRef);
2047
2048 /* This states should be set on broadcast */
2049 pHidState->guestState.fNumLockOn =
2050 pHidState->guestState.fCapsLockOn =
2051 pHidState->guestState.fScrollLockOn = false;
2052
2053 /* Finally, subscribe to USB HID notifications in order to prevent LED artifacts on
2054 automatic power management */
2055 if (darwinUsbHidSubscribeInterestNotifications(pHidState) == 0)
2056 return pHidState;
2057 }
2058 }
2059
2060 rc = IOHIDManagerClose(pHidState->hidManagerRef, 0);
2061 if (rc != kIOReturnSuccess)
2062 LogRel2(("Warning! Something went wrong in attempt to close HID device manager!\n"));
2063 }
2064
2065 CFRelease(deviceMatchingDictRef);
2066 }
2067
2068 CFRelease(pHidState->hidManagerRef);
2069 }
2070
2071 free(pHidState);
2072
2073 return NULL;
2074#else /* !VBOX_WITH_KBD_LEDS_SYNC */
2075 return NULL;
2076#endif
2077}
2078
2079
2080int DarwinHidDevicesApplyAndReleaseLedsState(void *pState)
2081{
2082#ifdef VBOX_WITH_KBD_LEDS_SYNC
2083 VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pState;
2084 IOReturn rc, rc2 = 0;
2085
2086 AssertReturn(pHidState, kIOReturnError);
2087
2088 darwinUsbHidUnsubscribeInterestNotifications(pHidState);
2089
2090 /* Need to unregister Carbon stuff first: */
2091 darwinRemoveCarbonHandler(pHidState);
2092
2093 CFDictionaryRef elementMatchingDict = darwinQueryLedElementMatchingDictionary();
2094 if (elementMatchingDict)
2095 {
2096 /* Restore LEDs: */
2097 for (CFIndex i = 0; i < CFArrayGetCount(pHidState->pDeviceCollection); i++)
2098 {
2099 /* Cycle through supported devices only. */
2100 VBoxKbdState_t *pKbd;
2101 pKbd = (VBoxKbdState_t *)CFArrayGetValueAtIndex(pHidState->pDeviceCollection, i);
2102
2103 if (pKbd)
2104 {
2105 rc = darwinSetDeviceLedsState(pKbd->pDevice,
2106 elementMatchingDict,
2107 pKbd->LED.fNumLockOn,
2108 pKbd->LED.fCapsLockOn,
2109 pKbd->LED.fScrollLockOn);
2110 if (rc != 0)
2111 {
2112 LogRel2(("Unable to restore led states for device (%d)!\n", (int)i));
2113 rc2 = kIOReturnError;
2114 }
2115
2116 IOHIDDeviceUnscheduleFromRunLoop(pKbd->pDevice, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
2117
2118 LogRel2(("Restored LEDs for KBD %d (%p): fNumLockOn=%s, fCapsLockOn=%s, fScrollLockOn=%s\n",
2119 (int)i, pKbd, VBOX_BOOL_TO_STR_STATE(pKbd->LED.fNumLockOn), VBOX_BOOL_TO_STR_STATE(pKbd->LED.fCapsLockOn),
2120 VBOX_BOOL_TO_STR_STATE(pKbd->LED.fScrollLockOn)));
2121
2122 free(pKbd);
2123 }
2124 }
2125
2126 CFRelease(elementMatchingDict);
2127 }
2128
2129 /* Free resources: */
2130 CFRelease(pHidState->pDeviceCollection);
2131
2132 rc = IOHIDManagerClose(pHidState->hidManagerRef, 0);
2133 if (rc != kIOReturnSuccess)
2134 {
2135 LogRel2(("Warning! Something went wrong in attempt to close HID device manager!\n"));
2136 rc2 = kIOReturnError;
2137 }
2138
2139 IOHIDManagerUnscheduleFromRunLoop(pHidState->hidManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
2140
2141 CFRelease(pHidState->hidManagerRef);
2142
2143 free(pHidState);
2144
2145 return rc2;
2146#else /* !VBOX_WITH_KBD_LEDS_SYNC */
2147 (void)pState;
2148 return 0;
2149#endif /* !VBOX_WITH_KBD_LEDS_SYNC */
2150}
2151
2152void DarwinHidDevicesBroadcastLeds(void *pState, bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn)
2153{
2154#ifdef VBOX_WITH_KBD_LEDS_SYNC
2155 VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pState;
2156 IOReturn rc;
2157
2158 AssertReturnVoid(pHidState);
2159 AssertReturnVoid(pHidState->pDeviceCollection);
2160
2161 CFDictionaryRef elementMatchingDict = darwinQueryLedElementMatchingDictionary();
2162 if (elementMatchingDict)
2163 {
2164 LogRel2(("Start LEDs broadcast: fNumLockOn=%s, fCapsLockOn=%s, fScrollLockOn=%s\n",
2165 VBOX_BOOL_TO_STR_STATE(fNumLockOn), VBOX_BOOL_TO_STR_STATE(fCapsLockOn), VBOX_BOOL_TO_STR_STATE(fScrollLockOn)));
2166
2167 for (CFIndex i = 0; i < CFArrayGetCount(pHidState->pDeviceCollection); i++)
2168 {
2169 /* Cycle through supported devices only. */
2170 VBoxKbdState_t *pKbd;
2171 pKbd = (VBoxKbdState_t *)CFArrayGetValueAtIndex(pHidState->pDeviceCollection, i);
2172
2173 if (pKbd && darwinHidDeviceSupported(pKbd->pDevice))
2174 {
2175 rc = darwinSetDeviceLedsState(pKbd->pDevice,
2176 elementMatchingDict,
2177 fNumLockOn,
2178 fCapsLockOn,
2179 fScrollLockOn);
2180 if (rc != 0)
2181 LogRel2(("Unable to restore led states for device (%d)!\n", (int)i));
2182 }
2183 }
2184
2185 LogRel2(("LEDs broadcast completed\n"));
2186
2187 CFRelease(elementMatchingDict);
2188 }
2189
2190 /* Dynamically attached device will use these states: */
2191 pHidState->guestState.fNumLockOn = fNumLockOn;
2192 pHidState->guestState.fCapsLockOn = fCapsLockOn;
2193 pHidState->guestState.fScrollLockOn = fScrollLockOn;
2194#else /* !VBOX_WITH_KBD_LEDS_SYNC */
2195 (void)fNumLockOn;
2196 (void)fCapsLockOn;
2197 (void)fScrollLockOn;
2198#endif /* !VBOX_WITH_KBD_LEDS_SYNC */
2199}
2200
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette