VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 3 weeks ago

Copyright year updates by scm.

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

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