Fixed bug 2157 - Caps Lock key produces key down & key up events while key is still pressed.
Tim McDaniel
Using checkkeys test app:
* Press and hold Caps Lock key.
* checkkeys reports a CapsLock key pressed event and a CapsLock key released event.
* Release Caps Lock key.
* checkkeys reports no further events.
This patch fixes OSX Caps Lock up/down event detection by installing a HID callback.
1.1 --- a/src/video/cocoa/SDL_cocoakeyboard.m Mon Oct 03 03:42:10 2016 -0700
1.2 +++ b/src/video/cocoa/SDL_cocoakeyboard.m Tue Oct 04 02:11:52 2016 -0700
1.3 @@ -29,6 +29,7 @@
1.4 #include "../../events/scancodes_darwin.h"
1.5
1.6 #include <Carbon/Carbon.h>
1.7 +#include <IOKit/hid/IOHIDLib.h>
1.8
1.9 /*#define DEBUG_IME NSLog */
1.10 #define DEBUG_IME(...)
1.11 @@ -183,6 +184,105 @@
1.12
1.13 @end
1.14
1.15 +/*------------------------------------------------------------------------------
1.16 +Set up a HID callback to properly detect Caps Lock up/down events.
1.17 +Derived from:
1.18 +http://stackoverflow.com/questions/7190852/using-iohidmanager-to-get-modifier-key-events
1.19 +*/
1.20 +
1.21 +static IOHIDManagerRef s_hidManager = NULL;
1.22 +
1.23 +static void
1.24 +HIDCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value)
1.25 +{
1.26 + IOHIDElementRef elem = IOHIDValueGetElement(value);
1.27 + if (IOHIDElementGetUsagePage(elem) != kHIDPage_KeyboardOrKeypad
1.28 + || IOHIDElementGetUsage(elem) != kHIDUsage_KeyboardCapsLock) {
1.29 + return;
1.30 + }
1.31 + int pressed = IOHIDValueGetIntegerValue(value);
1.32 + SDL_SendKeyboardKey(pressed ? SDL_PRESSED : SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
1.33 +}
1.34 +
1.35 +static CFDictionaryRef
1.36 +CreateHIDDeviceMatchingDictionary(UInt32 usagePage, UInt32 usage)
1.37 +{
1.38 + CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault,
1.39 + 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1.40 + if (dict) {
1.41 + CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage);
1.42 + if (number) {
1.43 + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number);
1.44 + CFRelease(number);
1.45 + number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
1.46 + if (number) {
1.47 + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number);
1.48 + CFRelease(number);
1.49 + return dict;
1.50 + }
1.51 + }
1.52 + CFRelease(dict);
1.53 + }
1.54 + return NULL;
1.55 +}
1.56 +
1.57 +static void
1.58 +QuitHIDCallback()
1.59 +{
1.60 + if (!s_hidManager) {
1.61 + return;
1.62 + }
1.63 + IOHIDManagerUnscheduleFromRunLoop(s_hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
1.64 + IOHIDManagerRegisterInputValueCallback(s_hidManager, NULL, NULL);
1.65 + IOHIDManagerClose(s_hidManager, 0);
1.66 + CFRelease(s_hidManager);
1.67 + s_hidManager = NULL;
1.68 +}
1.69 +
1.70 +static void
1.71 +InitHIDCallback()
1.72 +{
1.73 + s_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
1.74 + if (!s_hidManager) {
1.75 + return;
1.76 + }
1.77 + CFDictionaryRef keyboard = NULL, keypad = NULL;
1.78 + CFArrayRef matches = NULL;
1.79 + keyboard = CreateHIDDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard);
1.80 + if (!keyboard) {
1.81 + goto fail;
1.82 + }
1.83 + keypad = CreateHIDDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Keypad);
1.84 + if (!keypad) {
1.85 + goto fail;
1.86 + }
1.87 + CFDictionaryRef matchesList[] = { keyboard, keypad };
1.88 + matches = CFArrayCreate(kCFAllocatorDefault, (const void **)matchesList, 2, NULL);
1.89 + if (!matches) {
1.90 + goto fail;
1.91 + }
1.92 + IOHIDManagerSetDeviceMatchingMultiple(s_hidManager, matches);
1.93 + IOHIDManagerRegisterInputValueCallback(s_hidManager, HIDCallback, NULL);
1.94 + IOHIDManagerScheduleWithRunLoop(s_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
1.95 + if (IOHIDManagerOpen(s_hidManager, kIOHIDOptionsTypeNone) == kIOReturnSuccess) {
1.96 + goto cleanup;
1.97 + }
1.98 +
1.99 +fail:
1.100 + QuitHIDCallback();
1.101 +
1.102 +cleanup:
1.103 + if (matches) {
1.104 + CFRelease(matches);
1.105 + }
1.106 + if (keypad) {
1.107 + CFRelease(keypad);
1.108 + }
1.109 + if (keyboard) {
1.110 + CFRelease(keyboard);
1.111 + }
1.112 +}
1.113 +
1.114 /* This is a helper function for HandleModifierSide. This
1.115 * function reverts back to behavior before the distinction between
1.116 * sides was made.
1.117 @@ -320,24 +420,6 @@
1.118 }
1.119 }
1.120
1.121 -/* This is a helper function for DoSidedModifiers.
1.122 - * This function handles the CapsLock case.
1.123 - */
1.124 -static void
1.125 -HandleCapsLock(unsigned short scancode,
1.126 - unsigned int oldMods, unsigned int newMods)
1.127 -{
1.128 - unsigned int oldMask, newMask;
1.129 -
1.130 - oldMask = oldMods & NSAlphaShiftKeyMask;
1.131 - newMask = newMods & NSAlphaShiftKeyMask;
1.132 -
1.133 - if (oldMask != newMask) {
1.134 - SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_CAPSLOCK);
1.135 - SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
1.136 - }
1.137 -}
1.138 -
1.139 /* This function will handle the modifier keys and also determine the
1.140 * correct side of the key.
1.141 */
1.142 @@ -366,9 +448,6 @@
1.143
1.144 unsigned int i, bit;
1.145
1.146 - /* Handle CAPSLOCK separately because it doesn't have a left/right side */
1.147 - HandleCapsLock(scancode, oldMods, newMods);
1.148 -
1.149 /* Iterate through the bits, testing each against the old modifiers */
1.150 for (i = 0, bit = NSShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) {
1.151 unsigned int oldMask, newMask;
1.152 @@ -492,6 +571,8 @@
1.153
1.154 data->modifierFlags = [NSEvent modifierFlags];
1.155 SDL_ToggleModState(KMOD_CAPS, (data->modifierFlags & NSAlphaShiftKeyMask) != 0);
1.156 +
1.157 + InitHIDCallback();
1.158 }
1.159
1.160 void
1.161 @@ -617,6 +698,7 @@
1.162 void
1.163 Cocoa_QuitKeyboard(_THIS)
1.164 {
1.165 + QuitHIDCallback();
1.166 }
1.167
1.168 #endif /* SDL_VIDEO_DRIVER_COCOA */