VirtualBox

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

Last change on this file since 103538 was 103538, checked in by vboxsync, 3 months ago

FE/Qt: Moving out logging stuff from UIDefs.h to separate UILoggingDefs.h; This breaks dependency of UIDefs/UICommon headers from VBox/log.h

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

© 2023 Oracle
ContactPrivacy policyTerms of Use