DarwinKeyboard.cpp revision b8ad1b25be7672ea35c34fd7c09a982b71a506b5
/* $Id$ */
/** @file
* Common GUI Library - Darwin Keyboard routines.
*
* @todo Move this up somewhere so that the two SDL GUIs can use parts of this code too (-HID stuff).
*/
/*
* Copyright (C) 2006-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_GUI
#define VBOX_WITH_KBD_LEDS_SYNC
//#define VBOX_WITHOUT_KBD_LEDS_SYNC_FILTERING
#include "DarwinKeyboard.h"
#ifdef DEBUG_PRINTF
#endif
#ifdef VBOX_WITH_KBD_LEDS_SYNC
# include <iprt/semaphore.h>
# include <IOKit/IOMessage.h>
# include <IOKit/IOMessage.h>
#endif
#ifdef USE_HID_FOR_MODIFIERS
# include <mach/mach_error.h>
# include <CoreFoundation/CoreFoundation.h>
#endif
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <ApplicationServices/ApplicationServices.h>
#ifndef USE_HID_FOR_MODIFIERS
# include "CocoaEventHelper.h"
#endif
/* Private interface in 10.3 and later. */
typedef int CGSConnection;
typedef enum
{
extern CGSConnection _CGSDefaultConnection(void);
extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection Connection, CGSGlobalHotKeyOperatingMode *enmMode);
extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection Connection, CGSGlobalHotKeyOperatingMode enmMode);
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
#define QZ_RMETA 0x36
#define QZ_LMETA 0x37
#define QZ_LSHIFT 0x38
#define QZ_CAPSLOCK 0x39
#define QZ_LALT 0x3A
#define QZ_LCTRL 0x3B
#define QZ_RSHIFT 0x3C
#define QZ_RALT 0x3D
#define QZ_RCTRL 0x3E
/* Found the definition of the fn-key in:
* Maybe we need this in the future.*/
#define QZ_FN 0x3F
#define QZ_NUMLOCK 0x47
/** short hand for an extended key. */
#define K_EX VBOXKEY_EXTENDED
/** short hand for a modifier key. */
#define K_MOD VBOXKEY_MODIFIER
/** short hand for a lock key. */
#define K_LOCK VBOXKEY_LOCK
#ifdef USE_HID_FOR_MODIFIERS
/** An attempt at catching reference leaks. */
#define MY_CHECK_CREFS(cRefs) do { AssertMsg(cRefs < 25, ("%ld\n", cRefs)); NOREF(cRefs); } while (0)
#endif
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/**
* This is derived partially from SDL_QuartzKeys.h and partially from testing.
*
* (The funny thing about the virtual scan codes on the mac is that they aren't
* offically documented, which is rather silly to say the least. Thus, the need
* for looking at SDL and other odd places for docs.)
*/
static const uint16_t g_aDarwinToSet1[] =
{
/* set-1 SDL_QuartzKeys.h */
0x1e, /* QZ_a 0x00 */
0x1f, /* QZ_s 0x01 */
0x20, /* QZ_d 0x02 */
0x21, /* QZ_f 0x03 */
0x23, /* QZ_h 0x04 */
0x22, /* QZ_g 0x05 */
0x2c, /* QZ_z 0x06 */
0x2d, /* QZ_x 0x07 */
0x2e, /* QZ_c 0x08 */
0x2f, /* QZ_v 0x09 */
0x56, /* between lshift and z. 'INT 1'? */
0x30, /* QZ_b 0x0B */
0x10, /* QZ_q 0x0C */
0x11, /* QZ_w 0x0D */
0x12, /* QZ_e 0x0E */
0x13, /* QZ_r 0x0F */
0x15, /* QZ_y 0x10 */
0x14, /* QZ_t 0x11 */
0x02, /* QZ_1 0x12 */
0x03, /* QZ_2 0x13 */
0x04, /* QZ_3 0x14 */
0x05, /* QZ_4 0x15 */
0x07, /* QZ_6 0x16 */
0x06, /* QZ_5 0x17 */
0x0d, /* QZ_EQUALS 0x18 */
0x0a, /* QZ_9 0x19 */
0x08, /* QZ_7 0x1A */
0x0c, /* QZ_MINUS 0x1B */
0x09, /* QZ_8 0x1C */
0x0b, /* QZ_0 0x1D */
0x1b, /* QZ_RIGHTBRACKET 0x1E */
0x18, /* QZ_o 0x1F */
0x16, /* QZ_u 0x20 */
0x1a, /* QZ_LEFTBRACKET 0x21 */
0x17, /* QZ_i 0x22 */
0x19, /* QZ_p 0x23 */
0x1c, /* QZ_RETURN 0x24 */
0x26, /* QZ_l 0x25 */
0x24, /* QZ_j 0x26 */
0x28, /* QZ_QUOTE 0x27 */
0x25, /* QZ_k 0x28 */
0x27, /* QZ_SEMICOLON 0x29 */
0x2b, /* QZ_BACKSLASH 0x2A */
0x33, /* QZ_COMMA 0x2B */
0x35, /* QZ_SLASH 0x2C */
0x31, /* QZ_n 0x2D */
0x32, /* QZ_m 0x2E */
0x34, /* QZ_PERIOD 0x2F */
0x0f, /* QZ_TAB 0x30 */
0x39, /* QZ_SPACE 0x31 */
0x29, /* QZ_BACKQUOTE 0x32 */
0x0e, /* QZ_BACKSPACE 0x33 */
0x9c, /* QZ_IBOOK_ENTER 0x34 */
0x01, /* QZ_ESCAPE 0x35 */
0, /* */
0, /* */
0x53, /* QZ_KP_PERIOD 0x41 */
0, /* */
0x37, /* QZ_KP_MULTIPLY 0x43 */
0, /* */
0x4e, /* QZ_KP_PLUS 0x45 */
0, /* */
0, /* */
0, /* */
0, /* */
0, /* */
0x4a, /* QZ_KP_MINUS 0x4E */
0, /* */
0, /* */
0x0d/*?*/, /* QZ_KP_EQUALS 0x51 */
0x52, /* QZ_KP0 0x52 */
0x4f, /* QZ_KP1 0x53 */
0x50, /* QZ_KP2 0x54 */
0x51, /* QZ_KP3 0x55 */
0x4b, /* QZ_KP4 0x56 */
0x4c, /* QZ_KP5 0x57 */
0x4d, /* QZ_KP6 0x58 */
0x47, /* QZ_KP7 0x59 */
0, /* */
0x48, /* QZ_KP8 0x5B */
0x49, /* QZ_KP9 0x5C */
0x7d, /* yen, | (JIS) 0x5D */
0x73, /* _, ro (JIS) 0x5E */
0, /* */
0x3f, /* QZ_F5 0x60 */
0x40, /* QZ_F6 0x61 */
0x41, /* QZ_F7 0x62 */
0x3d, /* QZ_F3 0x63 */
0x42, /* QZ_F8 0x64 */
0x43, /* QZ_F9 0x65 */
0x57, /* QZ_F11 0x67 */
0x63, /* QZ_F16 0x6A */
0, /* */
0x44, /* QZ_F10 0x6D */
0x58, /* QZ_F12 0x6F */
0, /* */
0/* 0xe1,0x1d,0x45*/, /* QZ_PAUSE 0x71 */
0x3e, /* QZ_F4 0x76 */
0x3c, /* QZ_F2 0x78 */
0x3b, /* QZ_F1 0x7A */
};
/** Whether we've connected or not. */
static bool g_fConnectedToCGS = false;
/** Cached connection. */
static CGSConnection g_CGSConnection;
#ifdef USE_HID_FOR_MODIFIERS
/** The IO Master Port. */
/** Keyboards in the cache. */
static unsigned g_cKeyboards = 0;
/** Array of cached keyboard data. */
static struct KeyboardCacheData
{
/** The device interface. */
/** The queue interface. */
/* cookie translation array. */
struct KeyboardCacheCookie
{
/** The cookie. */
/** The corresponding modifier mask. */
} aCookies[64];
/** Number of cookies in the array. */
unsigned cCookies;
} g_aKeyboards[128];
/** The keyboard cache creation timestamp. */
static uint64_t g_u64KeyboardTS = 0;
/** HID queue status. */
static bool g_fHIDQueueEnabled;
/** The current modifier mask. */
static uint32_t g_fHIDModifierMask;
/** The old modifier mask. */
static uint32_t g_fOldHIDModifierMask;
#endif /* USE_HID_FOR_MODIFIERS */
#ifdef VBOX_WITH_KBD_LEDS_SYNC
/* HID LEDs synchronization data: LED states. */
typedef struct VBoxLedState_t {
bool fNumLockOn; /** A state of NUM LOCK */
bool fCapsLockOn; /** A state of CAPS LOCK */
bool fScrollLockOn; /** A state of SCROLL LOCK */
/* HID LEDs synchronization data: keyboard states. */
typedef struct VBoxKbdState_t {
void *pParentContainer; /** A pointer to a VBoxHidsState_t instance where VBoxKbdState_t instance is stored */
CFIndex idxPosition; /** Position in global storage (used to simplify CFArray navigation when removing detached device) */
/* A struct that used to pass input event info from IOKit callback to a Carbon one */
typedef struct VBoxKbdEvent_t {
/* HID LEDs synchronization data: IOKit specific data. */
typedef struct VBoxHidsState_t {
VBoxLedState_t guestState; /** LED states that were stored during last broadcast and reflect a guest LED states */
CFMutableArrayRef pFifoEventQueue; /** This queue will be appended in IOKit input callback. Carbon input callback will extract data from it */
io_iterator_t pUsbHidDeviceMatchNotify; /** IOService notification reference: USB HID device matching */
io_iterator_t pUsbHidGeneralInterestNotify; /** IOService notification reference: USB HID general interest
notifications (IOService messages) */
IONotificationPortRef pNotificationPrortRef; /** IOService notification port reference: used for both notification
types - device match and device general interest message */
/* Carbon events data */
#endif /* !VBOX_WITH_KBD_LEDS_SYNC */
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
#ifdef USE_HID_FOR_MODIFIERS
static void darwinBruteForcePropertySearch(CFDictionaryRef DictRef, struct KeyboardCacheData *pKeyboardEntry);
#endif
/**
* Converts a darwin (virtual) key code to a set 1 scan code.
*
* @returns set 1 scan code.
* @param uKeyCode The darwin key code.
*/
unsigned DarwinKeycodeToSet1Scancode(unsigned uKeyCode)
{
return 0;
return g_aDarwinToSet1[uKeyCode];
}
/**
* Converts a single modifier to a set 1 scan code.
*
* @returns Set 1 scan code.
* @returns ~0U if more than one modifier is set.
* @param fModifiers The darwin modifier mask.
*/
{
else
return uScanCode;
}
/**
* Converts a single modifier to a darwin keycode.
*
* @returns Darwin keycode.
* @returns 0 if none of the support masks were set.
* @returns ~0U if more than one modifier is set.
* @param fModifiers The darwin modifier mask.
*/
{
unsigned uKeyCode;
/** @todo find symbols for these keycodes... */
fModifiers &= shiftKey | rightShiftKey | controlKey | rightControlKey | optionKey | rightOptionKey | cmdKey
| kEventKeyModifierRightCmdKeyMask | kEventKeyModifierNumLockMask | alphaLock | kEventKeyModifierFnMask;
if (fModifiers == shiftKey)
else if (fModifiers == rightShiftKey)
else if (fModifiers == controlKey)
else if (fModifiers == rightControlKey)
else if (fModifiers == optionKey)
else if (fModifiers == rightOptionKey)
else if (fModifiers == cmdKey)
else if (fModifiers == alphaLock)
else if (fModifiers == kEventKeyModifierNumLockMask)
else if (fModifiers == kEventKeyModifierFnMask)
else if (fModifiers == 0)
uKeyCode = 0;
else
uKeyCode = ~0U; /* multiple */
return uKeyCode;
}
/**
* Converts a darwin keycode to a modifier mask.
*
* @returns Darwin modifier mask.
* @returns 0 if the keycode isn't a modifier we know.
* @param uKeyCode The darwin
*/
{
/** @todo find symbols for these keycodes... */
fModifiers = cmdKey;
else if (uKeyCode == QZ_CAPSLOCK)
else if (uKeyCode == QZ_NUMLOCK)
else
fModifiers = 0;
return fModifiers;
}
/**
* Disables or enabled global hot keys.
*
* @param fDisable Pass 'true' to disable the hot keys, pass 'false' to re-enable them.
*/
void DarwinDisableGlobalHotKeys(bool fDisable)
{
static unsigned s_cComplaints = 0;
/*
* Lazy connect to the core graphics service.
*/
if (!g_fConnectedToCGS)
{
g_fConnectedToCGS = true;
}
/*
* Get the current mode.
*/
if ( enmMode != kCGSGlobalHotKeyEnable
{
if (s_cComplaints++ < 32)
return;
}
/*
* Calc the new mode.
*/
if (fDisable)
{
if (enmMode != kCGSGlobalHotKeyEnable)
return;
}
else
{
return;
}
/*
* Try set it and check the actual result.
*/
if (enmNewMode != enmMode)
{
/* If the screensaver kicks in we should ignore failure here. */
if (s_cComplaints++ < 32)
LogRel(("DarwinDisableGlobalHotKeys: Failed to change mode; enmNewMode=%d enmMode=%d\n", enmNewMode, enmMode));
}
}
#ifdef USE_HID_FOR_MODIFIERS
/**
* Callback function for consuming queued events.
*
* @param pvTarget queue?
* @param rcIn ?
* @param pvRefcon Pointer to the keyboard cache entry.
* @param pvSender ?
*/
{
return;
/*
* Consume the events.
*/
for (;;)
{
#ifdef DEBUG_PRINTF
#endif
AbsoluteTime ZeroTime = {0,0};
IOReturn rc = (*pKeyboardEntry->ppHidQueueInterface)->getNextEvent(pKeyboardEntry->ppHidQueueInterface,
if (rc != kIOReturnSuccess)
break;
/* Translate the cookie value to a modifier mask. */
unsigned i = pKeyboardEntry->cCookies;
while (i-- > 0)
{
{
break;
}
}
/*
* Adjust the modifier mask.
*
* Note that we don't bother to deal with anyone pressing the same modifier
* on 2 or more keyboard. That's not worth the effort involved.
*/
else
g_fHIDModifierMask &= ~fMask;
#ifdef DEBUG_PRINTF
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);
#endif
}
#ifdef DEBUG_PRINTF
#endif
}
/**
* Element enumeration callback.
*/
{
darwinBruteForcePropertySearch((CFMutableDictionaryRef)pvValue, (struct KeyboardCacheData *)pvCacheEntry);
}
/**
* Recurses thru the keyboard properties looking for certain keys.
*
* @remark Yes, this can probably be done in a more efficient way. If you
* know how to do this, don't hesitate to let us know!
*/
static void darwinBruteForcePropertySearch(CFDictionaryRef DictRef, struct KeyboardCacheData *pKeyboardEntry)
{
/*
* Check for the usage page and usage key we want.
*/
long lUsage;
if ( ObjRef
{
switch (lUsage)
{
{
long lPage;
if ( !ObjRef
|| lPage != kHIDPage_KeyboardOrKeypad)
break;
{
AssertMsgFailed(("too many cookies!\n"));
break;
}
/*
* Get the cookie and modifier mask.
*/
long lCookie;
if ( !ObjRef
break;
switch (lUsage)
{
}
/*
* If we've got a queue, add the cookie to the queue.
*/
{
IOReturn rc = (*pKeyboardEntry->ppHidQueueInterface)->addElement(pKeyboardEntry->ppHidQueueInterface, (IOHIDElementCookie)lCookie, 0);
#ifdef DEBUG_PRINTF
#endif
}
/*
* Add the cookie to the keyboard entry.
*/
break;
}
}
}
/*
* Get the elements key and recursively iterate the elements looking
* for they key cookies.
*/
if ( ObjRef
{
}
}
/**
* Creates a keyboard cache entry.
*
* @returns true if the entry was created successfully, otherwise false.
* @param pKeyboardEntry Pointer to the entry.
* @param KeyboardDevice The keyboard device to create the entry for.
*
*/
static bool darwinHIDKeyboardCacheCreateEntry(struct KeyboardCacheData *pKeyboardEntry, io_object_t KeyboardDevice)
{
unsigned long cRefs = 0;
/*
* Query the HIDDeviceInterface for this HID (keyboard) object.
*/
if (rc == kIOReturnSuccess)
{
(LPVOID *)&ppHidDeviceInterface);
{
if (rc == kIOReturnSuccess)
{
/*
* create a removal callback.
*/
/** @todo */
/*
* Create the queue so we can insert elements while searching the properties.
*/
IOHIDQueueInterface **ppHidQueueInterface = (*ppHidDeviceInterface)->allocQueue(ppHidDeviceInterface);
if (ppHidQueueInterface)
{
if (rc != kIOReturnSuccess)
{
}
}
else
AssertFailed();
/*
* Brute force getting of attributes.
*/
/** @todo read up on how to do this in a less resource intensive way! Suggestions are welcome! */
kern_return_t krc = IORegistryEntryCreateCFProperties(KeyboardDevice, &PropertiesRef, kCFAllocatorDefault, kNilOptions);
if (krc == KERN_SUCCESS)
{
}
else
if (ppHidQueueInterface)
{
/*
* Now install our queue callback.
*/
if (rc == kIOReturnSuccess)
{
}
/*
* Now install our queue callback.
*/
rc = (*ppHidQueueInterface)->setEventCallout(ppHidQueueInterface, darwinQueueCallback, ppHidQueueInterface, pKeyboardEntry);
if (rc != kIOReturnSuccess)
}
/*
* Complete the new keyboard cache entry.
*/
return true;
}
}
else
}
else
return false;
}
/**
* Destroys a keyboard cache entry.
*
* @param pKeyboardEntry The entry.
*/
{
unsigned long cRefs;
/*
* Destroy the queue
*/
{
/* stop it just in case we haven't done so. doesn't really matter I think. */
/* deal with the run loop source. */
CFRunLoopSourceRef RunLoopSrcRef = (*ppHidQueueInterface)->getAsyncEventSource(ppHidQueueInterface);
if (RunLoopSrcRef)
{
}
/* dispose of and release the queue. */
}
/*
* Release the removal hook?
*/
/** @todo */
/*
* Close and release the device interface.
*/
{
}
}
/**
* Zap the keyboard cache.
*/
static void darwinHIDKeyboardCacheZap(void)
{
/*
* Release the old cache data first.
*/
while (g_cKeyboards > 0)
{
unsigned i = --g_cKeyboards;
}
}
/**
* Updates the cached keyboard data.
*
* @todo The current implementation is very brute force...
* Rewrite it so that it doesn't flush the cache completely but simply checks whether
* anything has changed in the HID config. With any luck, there might even be a callback
* or something we can poll for HID config changes...
* setRemovalCallback() is a start...
*/
static void darwinHIDKeyboardCacheDoUpdate(void)
{
/*
* Dispense with the old cache data.
*/
/*
* Open the master port on the first invocation.
*/
if (!g_MasterPort)
{
}
/*
* Create a matching dictionary for searching for keyboards devices.
*/
/*
* Perform the search and get a collection of keyboard devices.
*/
/*
* Enumerate the keyboards and query the cache data.
*/
unsigned i = 0;
while ( i < RT_ELEMENTS(g_aKeyboards)
{
i++;
}
g_cKeyboards = i;
}
/**
* Updates the keyboard cache if it's time to do it again.
*/
static void darwinHIDKeyboardCacheUpdate(void)
{
if ( !g_cKeyboards
/*|| g_u64KeyboardTS - RTTimeMilliTS() > 7500*/ /* 7.5sec */)
}
/**
* Queries the modifier keys from the (IOKit) HID Manager.
*
*/
static UInt32 darwinQueryHIDModifiers(void)
{
/*
* Iterate thru the keyboards collecting their modifier masks.
*/
UInt32 fHIDModifiers = 0;
unsigned i = g_cKeyboards;
while (i-- > 0)
{
if (!ppHidDeviceInterface)
continue;
unsigned j = g_aKeyboards[i].cCookies;
while (j-- > 0)
{
&HidEvent);
if (rc == kIOReturnSuccess)
{
}
else
}
}
return fHIDModifiers;
}
#endif /* USE_HID_FOR_MODIFIERS */
/**
* Left / right adjust the modifier mask using the current
* keyboard state.
*
* @param fModifiers The mask to adjust.
* @param pvCocoaEvent The associated Cocoa keyboard event. This is NULL
* when using HID for modifier corrections.
*
*/
{
/*
* Check if there is anything to adjust and perform the adjustment.
*/
if (fModifiers & (shiftKey | rightShiftKey | controlKey | rightControlKey | optionKey | rightOptionKey | cmdKey | kEventKeyModifierRightCmdKeyMask))
{
#ifndef USE_HID_FOR_MODIFIERS
/*
* Convert the Cocoa modifiers to Carbon ones (the Cocoa modifier
* definitions are tucked away in Objective-C headers, unfortunately).
*
* Update: CGEventTypes.h includes what looks like the Cocoa modifiers
* and the NX_* defines should be available as well. We should look
* into ways to intercept the CG (core graphics) events in the Carbon
* based setup and get rid of all this HID mess.
*/
//::darwinPrintEvent("dbg-adjMods: ", pvCocoaEvent);
#else /* USE_HID_FOR_MODIFIERS */
/*
* Update the keyboard cache.
*/
#endif /* USE_HID_FOR_MODIFIERS */
#ifdef DEBUG_PRINTF
#endif
{
}
{
}
{
}
{
}
#ifdef DEBUG_PRINTF
#endif
}
return fModifiers;
}
/**
* Start grabbing keyboard events.
*
* This only concerns itself with modifiers and disabling global hotkeys (if requested).
*
* @param fGlobalHotkeys Whether to disable global hotkeys or not.
*/
void DarwinGrabKeyboard(bool fGlobalHotkeys)
{
#ifdef USE_HID_FOR_MODIFIERS
/*
* Update the keyboard cache.
*/
/*
* Start the keyboard queues and query the current mask.
*/
g_fHIDQueueEnabled = true;
unsigned i = g_cKeyboards;
while (i-- > 0)
{
if (g_aKeyboards[i].ppHidQueueInterface)
}
#endif /* USE_HID_FOR_MODIFIERS */
/*
* Disable hotkeys if requested.
*/
if (fGlobalHotkeys)
DarwinDisableGlobalHotKeys(true);
}
/**
* Reverses the actions taken by DarwinGrabKeyboard.
*/
void DarwinReleaseKeyboard(void)
{
LogFlow(("DarwinReleaseKeyboard\n"));
/*
* Re-enable hotkeys.
*/
DarwinDisableGlobalHotKeys(false);
#ifdef USE_HID_FOR_MODIFIERS
/*
* Stop and drain the keyboard queues.
*/
g_fHIDQueueEnabled = false;
#if 0
unsigned i = g_cKeyboards;
while (i-- > 0)
{
if (g_aKeyboards[i].ppHidQueueInterface)
{
/* drain it */
unsigned c = 0;
do
{
AbsoluteTime MaxTime = {0,0};
} while ( rc == kIOReturnSuccess
&& c++ < 32);
}
}
#else
/*
* Kill the keyboard cache.
* This will hopefully fix the crash in getElementValue()/fillElementValue()...
*/
#endif
/* Clear the modifier mask. */
g_fHIDModifierMask = 0;
#endif /* USE_HID_FOR_MODIFIERS */
}
#ifdef VBOX_WITH_KBD_LEDS_SYNC
/** Prepare dictionary that will be used to match HID LED device(s) while discovering. */
{
/* Use two (key, value) pairs:
* - (kIOHIDDeviceUsagePageKey, kHIDPage_GenericDesktop),
* - (kIOHIDDeviceUsageKey, kHIDUsage_GD_Keyboard). */
usagePageKeyCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePageKeyCFNumberValue);
{
usageKeyCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usageKeyCFNumberValue);
if (usageKeyCFNumberRef)
{
(const void **)dictionaryKeys,
(const void **)dictionaryVals,
2, /** two (key, value) pairs */
{
return deviceMatchingDictRef;
}
}
}
return NULL;
}
/** Prepare dictionary that will be used to match HID LED device element(s) while discovering. */
{
/* Use only one (key, value) pair to match LED device element:
* - (kIOHIDElementUsagePageKey, kHIDPage_LEDs). */
usagePageKeyCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePageKeyCFNumberValue);
{
(const void **)dictionaryKeys,
(const void **)dictionaryVals,
1, /** one (key, value) pair */
{
return elementMatchingDictRef;
}
}
return NULL;
}
/** Turn ON or OFF a particular LED. */
static int darwinLedElementSetValue(IOHIDDeviceRef hidDevice, IOHIDElementRef element, bool fEnabled)
{
/* Try to resume suspended keyboard devices. Abort if failed in order to avoid GUI freezes. */
int rc1 = SUPR3ResumeSuspendedKeyboards();
if (RT_FAILURE(rc1))
return rc1;
if (valueRef)
{
if (rc != kIOReturnSuccess)
LogRel2(("Warning! Something went wrong in attempt to turn %s HID device led (error %d)!\n", ((fEnabled) ? "on" : "off"), rc));
else
LogRel2(("Led (%d) is turned %s\n", (int)IOHIDElementGetUsage(element), ((fEnabled) ? "on" : "off")));
}
return rc;
}
/** Get state of a particular led. */
static int darwinLedElementGetValue(IOHIDDeviceRef hidDevice, IOHIDElementRef element, bool *fEnabled)
{
/* Try to resume suspended keyboard devices. Abort if failed in order to avoid GUI freezes. */
int rc1 = SUPR3ResumeSuspendedKeyboards();
if (RT_FAILURE(rc1))
return rc1;
if (rc == kIOReturnSuccess)
{
switch (integerValue)
{
case 0:
*fEnabled = false;
break;
case 1:
*fEnabled = true;
break;
default:
rc = kIOReturnError;
}
}
return rc;
}
/** Set corresponding states from NumLock, CapsLock and ScrollLock leds. */
{
int rc2 = 0;
matchingElementsArrayRef = IOHIDDeviceCopyMatchingElements(hidDevice, elementMatchingDict, kIOHIDOptionsTypeNone);
{
/* Cycle though all the elements we found */
{
int rc = 0;
switch (usage)
{
case kHIDUsage_LED_NumLock:
break;
case kHIDUsage_LED_CapsLock:
break;
case kHIDUsage_LED_ScrollLock:
break;
}
if (rc != 0)
{
}
}
}
return rc2;
}
/** Get corresponding states for NumLock, CapsLock and ScrollLock leds. */
{
int rc2 = 0;
matchingElementsArrayRef = IOHIDDeviceCopyMatchingElements(hidDevice, elementMatchingDict, kIOHIDOptionsTypeNone);
{
/* Cycle though all the elements we found */
{
int rc = 0;
switch (usage)
{
case kHIDUsage_LED_NumLock:
break;
case kHIDUsage_LED_CapsLock:
break;
case kHIDUsage_LED_ScrollLock:
break;
}
if (rc != 0)
{
}
}
}
return rc2;
}
/** Get integer property of HID device */
{
AssertReturn(pProperty, 0);
if (pNumberRef)
{
{
return value;
}
}
return 0;
}
/** Get HID Vendor ID */
{
}
/** Get HID Product ID */
{
}
/** Get HID Location ID */
{
}
/** Some keyboard devices might freeze after LEDs manipulation. We filter out such devices here.
* In the list below, devices that known to have such issues. If you want to add new device,
* then add it here. Currently, we only filter devices by Vendor ID.
* In future it might make sense to take Product ID into account as well. */
{
bool fSupported = true;
{
fSupported = false;
}
{
fSupported = false;
}
LogRel2(("HID device [VendorID=0x%X, ProductId=0x%X] %s in the list of supported devices.\n", vendorId, productId, (fSupported ? "is" : "is not")));
return fSupported;
#else /* !VBOX_WITH_KBD_LEDS_SYNC_FILTERING */
return true;
#endif
}
/** IOKit key press callback helper: take care about key-down event.
* This code should be executed within a critical section under pHidState->fifoEventQueueLock. */
static void darwinHidInputCbKeyDown(VBoxKbdState_t *pKbd, uint32_t iKeyCode, VBoxHidsState_t *pHidState)
{
if (pEvent)
{
/* Queue Key-Down event. */
}
else
LogRel2(("IOHID: Modifier Key-Up event. Unable to find memory for KBD %d event\n", (int)pKbd->idxPosition));
}
/** IOkit and Carbon key press callbacks helper: CapsLock timeout checker.
*
* Returns FALSE if CAPS LOCK timeout not occurred and its state still was not switched (Apple kbd).
* Returns TRUE if CAPS LOCK timeout occurred and its state was switched (Apple kbd).
* Returns TRUE for non-Apple kbd. */
{
/* CapsLock timeout is only applicable if conditions
* below are satisfied:
*
* a) Key pressed on Apple keyboard
* b) CapsLed is OFF at the moment when CapsLock key is pressed
*/
/* Apple keyboard */
if (fAppleKeyboard && !fCapsLed)
{
return false;
}
return true;
}
/** IOKit key press callback helper: take care about key-up event.
* This code should be executed within a critical section under pHidState->fifoEventQueueLock. */
static void darwinHidInputCbKeyUp(VBoxKbdState_t *pKbd, uint32_t iKeyCode, VBoxHidsState_t *pHidState)
{
/* Key-up event assumes that key-down event occured previously. If so, an event
* data should be in event queue. Attempt to find it. */
{
VBoxKbdEvent_t *pCachedEvent = (VBoxKbdEvent_t *)CFArrayGetValueAtIndex(pHidState->pFifoEventQueue, i);
{
iQueue = i;
break;
}
}
/* Event found. */
if (pEvent)
{
/* NUM LOCK should not have timeout and its press should immidiately trigger Carbon callback.
* Therefore, if it is still in queue this is a problem because it was not handled by Carbon callback.
* This mean that NUM LOCK is most likely out of sync. */
if (iKeyCode == kHIDUsage_KeypadNumLock)
{
LogRel2(("IOHID: KBD %d: Modifier Key-Up event. Key-Down event was not habdled by Carbon callback. "
}
else if (iKeyCode == kHIDUsage_KeyboardCapsLock)
{
/* If CAPS LOCK key-press event still not match CAPS LOCK timeout criteria, Carbon callback
* should not be triggered for this event at all. Threfore, event should be removed from queue. */
{
LogRel2(("IOHID: KBD %d: Modifier Key-Up event on Apple keyboard. Key-Down event was triggered %llu ms "
}
else
{
/* CAPS LOCK key-press event matches to CAPS LOCK timeout criteria and still present in queue.
* This might mean that Carbon callback was triggered for this event, but cached keyboard state was not updated.
* It also might mean that Carbon callback still was not triggered, but it will be soon.
* Threfore, CAPS LOCK might be out of sync. */
LogRel2(("IOHID: KBD %d: Modifier Key-Up event. Key-Down event was triggered %llu ms "
"ago and still was not handled by Carbon callback. CAPS LOCK might out of sync if "
"Carbon will not handle this\n", (int)pKbd->idxPosition, RTTimeSystemMilliTS() - pEvent->tsKeyDown));
}
}
}
else
LogRel2(("IOHID: KBD %d: Modifier Key-Up event. Modifier state change was "
}
/** IOKit key press callback. Triggered before Carbon callback. We remember which keyboard produced a keypress here. */
static void darwinHidInputCallback(void *pData, IOReturn unused, void *unused1, IOHIDValueRef pValueRef)
{
(void)unused;
(void)unused1;
if (IOHIDElementGetUsagePage(pElementRef) == kHIDPage_KeyboardOrKeypad) /* Keyboard or keypad event */
{
{
return ;
/* Handle corresponding event. */
if (fKeyDown)
else
}
else
LogRel2(("IOHID: No KBD: A modifier key has been pressed\n"));
}
}
/** Carbon key press callback helper: find last occured KBD event in queue
* (ignoring those events which do not match CAPS LOCK timeout criteria).
* Once event found, it is removed from queue. This code should be executed
* within a critical section under pHidState->fifoEventQueueLock. */
{
{
/* Paranoia: skip potentially dangerous data items. */
|| (pEvent->iKeyCode == kHIDUsage_KeyboardCapsLock && darwinKbdCapsEventMatches(pEvent, pHidState->guestState.fCapsLockOn)))
{
/* Found one. Remove it from queue. */
LogRel2(("CARBON: Found event in queue: %d (KBD %d, tsKeyDown=%llu, pressed %llu ms ago)\n", (int)i,
break;
}
else
LogRel2(("CARBON: Skip keyboard event from KBD %d, key pressed %llu ms ago\n",
}
return pEvent;
}
/** Carbon key press callback. Triggered after IOKit callback. */
static CGEventRef darwinCarbonCallback(CGEventTapProxy unused, CGEventType unused1, CGEventRef pEventRef, void *pData)
{
(void)unused;
(void)unused1;
return pEventRef;
if (key == kHIDUsage_KeyboardCapsLock ||
{
/* Attempt to find an event queued by IOKit callback. */
if (pEvent)
{
LogRel2(("CARBON: KBD %d: caps=%s, num=%s. tsKeyDown=%llu, tsKeyUp=%llu [tsDiff=%llu ms]. %d events in queue.\n",
/* Silently resync last touched KBD device */
if (pHidState)
{
if (elementMatchingDict)
{
}
}
}
else
LogRel2(("CARBON: No KBD to take care when modifier key has been pressed: caps=%s, num=%s (%d events in queue)\n",
VBOX_BOOL_TO_STR_STATE(fCaps), VBOX_BOOL_TO_STR_STATE(fNum), CFArrayGetCount(pHidState->pFifoEventQueue)));
}
return pEventRef;
}
/** Helper function to obtain interface for IOUSBInterface IOService. */
{
{
rc = (*ppPluginInterface)->QueryInterface(ppPluginInterface, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
(LPVOID *)&ppUsbDeviceInterface);
return ppUsbDeviceInterface;
else
LogRel2(("Failed to query plugin interface for USB device\n"));
}
else
LogRel2(("Failed to create plugin interface for USB device\n"));
return NULL;
}
/** Helper function for IOUSBInterface IOService general interest notification callback: resync LEDs. */
{
if (elementMatchingDict)
{
pHidState->guestState.fNumLockOn, pHidState->guestState.fCapsLockOn, pHidState->guestState.fScrollLockOn);
}
}
/** IOUSBInterface IOService general interest notification callback. When we receive it, we do
* silently resync kbd which has just changed its state. */
static void darwinUsbHidGeneralInterestCb(void *pData, io_service_t unused1, natural_t msg, void *unused2)
{
switch (msg)
{
{
LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessagePortHasBeenSuspended for KBD %d (Location ID: 0x%X)\n",
break;
}
{
LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessagePortHasBeenResumed for KBD %d (Location ID: 0x%X)\n",
break;
}
{
LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessagePortHasBeenReset for KBD %d (Location ID: 0x%X)\n",
break;
}
{
LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessageCompositeDriverReconfigured for KBD %d (Location ID: 0x%X)\n",
break;
}
{
LogRel2(("IOUSBInterface IOService general interest notification kIOMessageServiceWasClosed for KBD %d (Location ID: 0x%X)\n",
break;
}
default:
LogRel2(("IOUSBInterface IOService general interest notification 0x%X for KBD %d (Location ID: 0x%X)\n",
}
}
/** Get pre-cached KBD device by its Location ID. */
static VBoxKbdState_t * darwinUsbHidQueryKbdByLocationId(uint32_t idLocation, VBoxHidsState_t *pHidState)
{
{
{
return pKbd;
}
}
return NULL;
}
/** IOUSBInterface IOService match notification callback: issued when IOService instantinates.
* We subscribe to general interest notifications for available IOServices here. */
{
{
if (ppUsbDeviceInterface)
{
rc = (*ppUsbDeviceInterface)->GetLocationID (ppUsbDeviceInterface, &idLocation); AssertMsg(rc == 0, ("Failed to get Location ID"));
rc = (*ppUsbDeviceInterface)->GetDeviceClass (ppUsbDeviceInterface, &idDeviceClass); AssertMsg(rc == 0, ("Failed to get Device Class"));
rc = (*ppUsbDeviceInterface)->GetDeviceSubClass(ppUsbDeviceInterface, &idDeviceSubClass); AssertMsg(rc == 0, ("Failed to get Device Subclass"));
{
if (pKbd)
{
rc = IOServiceAddInterestNotification(pHidState->pNotificationPrortRef, service, kIOGeneralInterest,
LogRel2(("Found HID device at location 0x%X: class 0x%X, subclass 0x%X\n", idLocation, idDeviceClass, idDeviceSubClass));
}
}
rc = (*ppUsbDeviceInterface)->Release(ppUsbDeviceInterface); AssertMsg(rc == 0, ("Failed to release USB device interface"));
}
}
}
/** Register IOUSBInterface IOService match notification callback in order to recync KBD
* device when it reports state change. */
{
int rc = kIOReturnNoMemory;
if (pDictionary)
{
{
CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(pHidState->pNotificationPrortRef), kCFRunLoopDefaultMode);
{
LogRel2(("Successfully subscribed to IOUSBInterface IOService match notifications\n"));
}
else
LogRel2(("Failed to subscribe to IOUSBInterface IOService match notifications: subscription error 0x%X\n", rc));
}
else
LogRel2(("Failed to subscribe to IOUSBInterface IOService match notifications: unable to create notification port\n"));
}
else
LogRel2(("Failed to subscribe to IOUSBInterface IOService match notifications: no memory\n"));
return rc;
}
/** Remove IOUSBInterface IOService match notification subscription. */
{
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(pHidState->pNotificationPrortRef), kCFRunLoopDefaultMode);
LogRel2(("Successfully un-subscribed from IOUSBInterface IOService match notifications\n"));
}
/** This callback is called when user physically removes HID device. We remove device from cache here. */
{
(void)unused;
(void)unused1;
VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pKbd->pParentContainer; AssertReturnVoid(pHidState);
//if (RT_FAILURE(RTSemMutexRequest(pHidState->fifoEventQueueLock, RT_INDEFINITE_WAIT)))
// return ;
//RTSemMutexRelease(pHidState->fifoEventQueueLock);
}
/* Check if we already cached given device */
{
AssertReturn(pState, false);
{
return true;
}
return false;
}
/** Add device to cache. */
static void darwinHidAddDevice(VBoxHidsState_t *pHidState, IOHIDDeviceRef pDevice, bool fApplyLedState)
{
int rc;
{
{
if (pKbd)
{
/* Some Apple keyboards have CAPS LOCK key timeout. According to corresponding
* kext plist files, it is equals to 75 ms. For such devices we only add info into our FIFO event
* queue if the time between Key-Down and Key-Up events >= 75 ms. */
pKbd->cCapsLockTimeout = (darwinHidVendorId(pKbd->pDevice) == kIOUSBVendorIDAppleComputer) ? 75 : 0;
if (elementMatchingDict)
{
/* This should never happen, but if happened -- mark all the leds of current
* device as turned OFF. */
if (rc != 0)
{
LogRel2(("Unable to get leds state for device %d. Mark leds as turned off\n", (int)(pKbd->idxPosition)));
}
/* Register per-device removal callback */
/* Register per-device input callback */
LogRel2(("Saved LEDs for KBD %d (%p): fNumLockOn=%s, fCapsLockOn=%s, fScrollLockOn=%s\n",
(int)pKbd->idxPosition, pKbd, VBOX_BOOL_TO_STR_STATE(pKbd->LED.fNumLockOn), VBOX_BOOL_TO_STR_STATE(pKbd->LED.fCapsLockOn),
if (fApplyLedState)
{
if (rc != 0)
LogRel2(("Unable to apply guest state to newly attached device\n"));
}
return;
}
}
}
}
}
/** This callback is called when new HID device discovered by IOHIDManager. We add devices to cache here and only here! */
static void darwinHidMatchingCallback(void *pData, IOReturn unused, void *unused1, IOHIDDeviceRef pDevice)
{
(void)unused;
(void)unused1;
}
/** Register Carbon key press callback. */
{
/* Create FIFO event queue for keyboard events */
/* Create Lock for FIFO event queue */
{
LogRel2(("Unable to create Lock for FIFO event queue\n"));
return kIOReturnError;
}
pTapRef = CGEventTapCreate(kCGSessionEventTap, kCGTailAppendEventTap, 0, fMask, darwinCarbonCallback, (void *)pHidState);
if (pTapRef)
{
if (pLoopSourceRef)
{
CGEventTapEnable(pTapRef, true);
return 0;
}
else
LogRel2(("Unable to create a loop source\n"));
}
else
LogRel2(("Unable to create an event tap\n"));
return kIOReturnError;
}
/** Remove Carbon key press callback. */
{
}
#endif /* !VBOX_WITH_KBD_LEDS_SYNC */
/** Save the states of leds for all HID devices attached to the system and return it. */
void * DarwinHidDevicesKeepLedsState(void)
{
#ifdef VBOX_WITH_KBD_LEDS_SYNC
if (pHidState->hidManagerRef)
{
{
IOHIDManagerScheduleWithRunLoop(pHidState->hidManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
if (rc == kIOReturnSuccess)
{
if (pHidState->pDeviceCollection)
{
if (darwinAddCarbonHandler(pHidState) == 0)
{
/* Populate cache with HID devices */
if (pDevicesSet)
{
if (ppDevices)
{
}
}
IOHIDManagerRegisterDeviceMatchingCallback(pHidState->hidManagerRef, darwinHidMatchingCallback, (void *)pHidState);
/* This states should be set on broadcast */
/* Finally, subscribe to USB HID notifications in order to prevent LED artifacts on
automatic power management */
return pHidState;
}
}
if (rc != kIOReturnSuccess)
LogRel2(("Warning! Something went wrong in attempt to close HID device manager!\n"));
}
}
}
return NULL;
#else /* !VBOX_WITH_KBD_LEDS_SYNC */
return NULL;
#endif
}
/**
* Apply LEDs state stored in *pState and release resources aquired by *pState.
*
* @param pState Pointer to saved LEDs state
*
* @return 0 on success, error code otherwise.
*/
int DarwinHidDevicesApplyAndReleaseLedsState(void *pState)
{
#ifdef VBOX_WITH_KBD_LEDS_SYNC
/* Need to unregister Carbon stuff first */
if (elementMatchingDict)
{
/* Restore LEDs */
{
/* Cycle through supported devices only. */
if (pKbd)
{
if (rc != 0)
{
LogRel2(("Unable to restore led states for device (%d)!\n", (int)i));
}
LogRel2(("Restored LEDs for KBD %d (%p): fNumLockOn=%s, fCapsLockOn=%s, fScrollLockOn=%s\n",
(int)i, pKbd, VBOX_BOOL_TO_STR_STATE(pKbd->LED.fNumLockOn), VBOX_BOOL_TO_STR_STATE(pKbd->LED.fCapsLockOn),
}
}
}
/* Free resources */
if (rc != kIOReturnSuccess)
{
LogRel2(("Warning! Something went wrong in attempt to close HID device manager!\n"));
}
IOHIDManagerUnscheduleFromRunLoop(pHidState->hidManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
return rc2;
#else /* !VBOX_WITH_KBD_LEDS_SYNC */
(void)pState;
return 0;
#endif
}
/**
* Set states for host keyboard LEDs.
*
* NOTE: This function will set led values for all
* keyboard devices attached to the system.
*
* @param pState Pointer to saved LEDs state
* @param fNumLockOn Turn on NumLock led if TRUE, turn off otherwise
* @param fCapsLockOn Turn on CapsLock led if TRUE, turn off otherwise
* @param fScrollLockOn Turn on ScrollLock led if TRUE, turn off otherwise
*/
void DarwinHidDevicesBroadcastLeds(void *pState, bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn)
{
/* Temporary disabled */
#ifdef VBOX_WITH_KBD_LEDS_SYNC
if (elementMatchingDict)
{
LogRel2(("Start LEDs broadcast: fNumLockOn=%s, fCapsLockOn=%s, fScrollLockOn=%s\n",
VBOX_BOOL_TO_STR_STATE(fNumLockOn), VBOX_BOOL_TO_STR_STATE(fCapsLockOn), VBOX_BOOL_TO_STR_STATE(fScrollLockOn)));
{
/* Cycle through supported devices only. */
{
if (rc != 0)
LogRel2(("Unable to restore led states for device (%d)!\n", (int)i));
}
}
LogRel2(("LEDs broadcast completed\n"));
}
/* Dynamically attached device will use these states */
#else /* !VBOX_WITH_KBD_LEDS_SYNC */
(void)fNumLockOn;
(void)fCapsLockOn;
(void)fScrollLockOn;
#endif
}