Added support for several connected keyboards in HIDInputManager
This commit is contained in:
parent
b5008ba01b
commit
5fbf147cc0
@ -36,6 +36,7 @@
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <IOKit/hid/IOHIDManager.h>
|
||||
#include <IOKit/hid/IOHIDDevice.h>
|
||||
#include <vector>
|
||||
|
||||
namespace sf
|
||||
{
|
||||
@ -126,6 +127,27 @@ private :
|
||||
////////////////////////////////////////////////////////////
|
||||
void InitializeKeyboard();
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Load the given keyboard into myKeys
|
||||
///
|
||||
/// If the given keyboard has no key this function simply
|
||||
/// returns. FreeUp is _not_ called because this is not fatal.
|
||||
///
|
||||
/// \param keyboard Keyboard to load
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
void LoadKeyboard(IOHIDDeviceRef keyboard);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Load the given key into myKeys
|
||||
///
|
||||
/// FreeUp is _not_ called by this function.
|
||||
///
|
||||
/// \param key Key to load
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
void LoadKey(IOHIDElementRef key);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Release all resources
|
||||
///
|
||||
@ -146,13 +168,25 @@ private :
|
||||
static CFDictionaryRef CopyDevicesMaskForManager(UInt32 page, UInt32 usage);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Converte a HID key usage to it's conrresponding virtual code
|
||||
/// \brief Filter the devices and return them.
|
||||
///
|
||||
/// If something went wrong FreeUp is called
|
||||
///
|
||||
/// \param page HID page like kHIDPage_GenericDesktop
|
||||
/// \param usage HID usage page like kHIDUsage_GD_Keyboard or kHIDUsage_GD_Mouse
|
||||
/// \return a retained CFSetRef of IOHIDDeviceRef or NULL
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
CFSetRef CopyDevices(UInt32 page, UInt32 usage);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Converte a HID key usage to its corresponding virtual code
|
||||
///
|
||||
/// See IOHIDUsageTables.h
|
||||
///
|
||||
/// \param usage Any kHIDUsage_Keyboard* usage
|
||||
/// \return the virtual code associate to the given HID key usage
|
||||
/// or 0xff if it is associate to no virtual code
|
||||
/// \return the virtual code associate with the given HID key usage
|
||||
/// or 0xff if it is associate with no virtual code
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
static UInt8 UsageToVirtualCode(UInt32 usage);
|
||||
@ -166,9 +200,18 @@ private :
|
||||
CFDataRef myLayoutData; ///< CFData containing the layout
|
||||
UCKeyboardLayout* myLayout; ///< Current Keyboard Layout
|
||||
IOHIDManagerRef myManager; ///< HID Manager
|
||||
IOHIDElementRef myKeys[Keyboard::KeyCount]; ///< All the keys on the current keyboard
|
||||
/* myKeys index correspond to sf::Keyboard::Key enum */
|
||||
/* if no key is assigned to a key XYZ then myKeys[XYZ] = 0 */
|
||||
|
||||
typedef std::vector<IOHIDElementRef> IOHIDElements;
|
||||
IOHIDElements myKeys[Keyboard::KeyCount]; ///< All the keys on the current keyboard
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// myKeys' index corresponds to sf::Keyboard::Key enum.
|
||||
/// if no key is assigned with key XYZ then myKeys[XYZ].size() == 0.
|
||||
/// if there are several keyboards connected and several HID keys associate
|
||||
/// with the same sf::Keyboard::Key then myKeys[XYZ] contains all these
|
||||
/// HID keys.
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
};
|
||||
|
||||
} // namespace priv
|
||||
|
@ -28,7 +28,6 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Window/OSX/HIDInputManager.hpp>
|
||||
#include <SFML/System/Err.hpp>
|
||||
#include <algorithm>
|
||||
#include <AppKit/AppKit.h>
|
||||
|
||||
namespace sf
|
||||
@ -51,48 +50,42 @@ bool HIDInputManager::IsKeyPressed(Keyboard::Key key)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Have we an associate IOHIDElementRef to that key ?
|
||||
if (myKeys[key] == 0) { // No
|
||||
|
||||
sf::Err() << key
|
||||
<< " is not associate to any IOHIDElementRef."
|
||||
<< std::endl;
|
||||
|
||||
return false;
|
||||
|
||||
} else { // Yes
|
||||
// state = true if at least one corresponding HID key is pressed
|
||||
bool state = false;
|
||||
|
||||
for (IOHIDElements::iterator it = myKeys[key].begin(); it != myKeys[key].end(); ++it) {
|
||||
|
||||
IOHIDValueRef value = 0;
|
||||
|
||||
IOHIDDeviceRef device = IOHIDElementGetDevice(myKeys[key]);
|
||||
IOHIDDeviceGetValue(device, myKeys[key], &value);
|
||||
IOHIDDeviceRef device = IOHIDElementGetDevice(*it);
|
||||
IOHIDDeviceGetValue(device, *it, &value);
|
||||
|
||||
if (!value) {
|
||||
|
||||
// This means some kind of error / deconnection so we remove this
|
||||
// element from our keys
|
||||
|
||||
CFRelease(myKeys[key]);
|
||||
myKeys[key] = 0;
|
||||
CFRelease(*it);
|
||||
it = myKeys[key].erase(it);
|
||||
|
||||
sf::Err() << key
|
||||
<< " is dead (cannot access its value)."
|
||||
<< std::endl;
|
||||
|
||||
return false;
|
||||
|
||||
|
||||
} else if (IOHIDValueGetIntegerValue(value) == 1) {
|
||||
|
||||
// This means the key is pressed
|
||||
return true;
|
||||
state = true;
|
||||
break; // Stop here.
|
||||
|
||||
} else {
|
||||
|
||||
// This means the key is released
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
@ -116,9 +109,6 @@ HIDInputManager::HIDInputManager()
|
||||
, myLayout(0)
|
||||
, myManager(0)
|
||||
{
|
||||
// And initialize myKeys with 0s.
|
||||
std::fill(myKeys, myKeys + Keyboard::KeyCount, (IOHIDElementRef)0);
|
||||
|
||||
// Get the current keyboard layout
|
||||
TISInputSourceRef tis = TISCopyCurrentKeyboardLayoutInputSource();
|
||||
myLayoutData = (CFDataRef)TISGetInputSourceProperty(tis,
|
||||
@ -172,65 +162,60 @@ void HIDInputManager::InitializeKeyboard()
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// The purpose of this function is to initalize myKeys so we can get
|
||||
// the associate IOHIDElementRef of a sf::Keyboard::Key in constant time.
|
||||
// the associate IOHIDElementRef with a sf::Keyboard::Key in ~constant~ time.
|
||||
|
||||
|
||||
// Filter and keep only the keyboards
|
||||
CFDictionaryRef mask = CopyDevicesMaskForManager(kHIDPage_GenericDesktop,
|
||||
kHIDUsage_GD_Keyboard);
|
||||
|
||||
IOHIDManagerSetDeviceMatching(myManager, mask);
|
||||
|
||||
CFRelease(mask);
|
||||
mask = 0;
|
||||
|
||||
CFSetRef keyboards = IOHIDManagerCopyDevices(myManager);
|
||||
// Get only keyboards
|
||||
CFSetRef keyboards = CopyDevices(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard);
|
||||
if (keyboards == NULL) {
|
||||
sf::Err() << "Cannot find any keyboard (1)" << std::endl;
|
||||
FreeUp();
|
||||
|
||||
// FreeUp was already called
|
||||
return;
|
||||
}
|
||||
|
||||
// Is there at least one keyboard ?
|
||||
CFIndex keyboardCount = CFSetGetCount(keyboards);
|
||||
if (keyboardCount < 1) {
|
||||
sf::Err() << "Cannot find any keyboard (2)" << std::endl;
|
||||
CFRelease(keyboards);
|
||||
FreeUp();
|
||||
return;
|
||||
}
|
||||
CFIndex keyboardCount = CFSetGetCount(keyboards); // >= 1 (asserted by CopyDevices)
|
||||
|
||||
// Get the first keyboard
|
||||
// Get an iterable array
|
||||
CFTypeRef devicesArray[keyboardCount];
|
||||
CFSetGetValues(keyboards, devicesArray);
|
||||
|
||||
IOHIDDeviceRef keyboard = (IOHIDDeviceRef)devicesArray[0];
|
||||
for (CFIndex i = 0; i < keyboardCount; ++i) {
|
||||
|
||||
CFArrayRef keys = IOHIDDeviceCopyMatchingElements(keyboard,
|
||||
NULL,
|
||||
kIOHIDOptionsTypeNone);
|
||||
IOHIDDeviceRef keyboard = (IOHIDDeviceRef)devicesArray[i];
|
||||
|
||||
LoadKeyboard(keyboard);
|
||||
}
|
||||
|
||||
// Release unused stuff
|
||||
CFRelease(keyboards);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// At this point myKeys is filled with as many IOHIDElementRef as possible
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void HIDInputManager::LoadKeyboard(IOHIDDeviceRef keyboard)
|
||||
{
|
||||
CFArrayRef keys = IOHIDDeviceCopyMatchingElements(keyboard,
|
||||
NULL,
|
||||
kIOHIDOptionsTypeNone);
|
||||
if (keys == NULL) {
|
||||
sf::Err() << "We got a keyboard without any keys (1)" << std::endl;
|
||||
FreeUp();
|
||||
return;
|
||||
}
|
||||
|
||||
// How many elements are there ?
|
||||
CFIndex keysCount = CFArrayGetCount(keys);
|
||||
|
||||
|
||||
if (keysCount == 0) {
|
||||
sf::Err() << "We got a keyboard without any keys (2)" << std::endl;
|
||||
CFRelease(keys);
|
||||
FreeUp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Go through all connected elements.
|
||||
for (int i = 0; i < keysCount; ++i) {
|
||||
for (CFIndex i = 0; i < keysCount; ++i) {
|
||||
|
||||
IOHIDElementRef aKey = (IOHIDElementRef) CFArrayGetValueAtIndex(keys, i);
|
||||
|
||||
// Skip non-matching keys elements
|
||||
@ -238,131 +223,110 @@ void HIDInputManager::InitializeKeyboard()
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get its virtual code
|
||||
UInt32 usageCode = IOHIDElementGetUsage(aKey);
|
||||
UInt8 virtualCode = UsageToVirtualCode(usageCode);
|
||||
LoadKey(aKey);
|
||||
|
||||
if (virtualCode == 0xff) {
|
||||
continue; // no corresponding virtual code -> skip
|
||||
}
|
||||
|
||||
// Now translate the virtual code to unicode according to
|
||||
// the current keyboard layout
|
||||
|
||||
UInt32 deadKeyState = 0;
|
||||
// unicode string length is usually less or equal to 4
|
||||
UniCharCount maxStringLength = 4;
|
||||
UniCharCount actualStringLength = 0;
|
||||
UniChar unicodeString[maxStringLength];
|
||||
|
||||
OSStatus error;
|
||||
|
||||
error = UCKeyTranslate(myLayout, // current layout
|
||||
virtualCode, // our key
|
||||
kUCKeyActionDown, // or kUCKeyActionUp ?
|
||||
0x100, // no modifiers
|
||||
LMGetKbdType(), // keyboard's type
|
||||
kUCKeyTranslateNoDeadKeysBit,// some sort of option
|
||||
&deadKeyState, // unused stuff
|
||||
maxStringLength, // our memory limit
|
||||
&actualStringLength, // length of what we get
|
||||
unicodeString); // what we get
|
||||
|
||||
if (error == noErr) {
|
||||
// Translation went fine
|
||||
|
||||
// The corresponding SFML key code
|
||||
Keyboard::Key code = Keyboard::KeyCount;
|
||||
|
||||
// First we look if the key down is from a list of caracter
|
||||
// that depend on keyboard localization.
|
||||
if (actualStringLength > 0) {
|
||||
code = LocalizedKeys(unicodeString[0]);
|
||||
}
|
||||
|
||||
// The key is not a localized one, so we try to find a corresponding
|
||||
// code through virtual key code.
|
||||
if (code == Keyboard::KeyCount) {
|
||||
code = NonLocalizedKeys(virtualCode);
|
||||
}
|
||||
|
||||
// A code was found, wonderful!
|
||||
if (code != Keyboard::KeyCount) {
|
||||
|
||||
// Last step : verify that the key was not found twice or more
|
||||
|
||||
// Some keys (modifiers) appears to be twice on my keyboard
|
||||
// I've no idea why
|
||||
// if (myKeys[code] != 0) {
|
||||
// UInt32 firstUsageCode = IOHIDElementGetUsage(myKeys[code]);
|
||||
// sf::Err() << "The current keyboard has several times the "
|
||||
// << "same keys. Only the first one is considered "
|
||||
// << "as valid. sf::Keyboard::Key is "
|
||||
// << code
|
||||
// << ", virtual key code is 0x"
|
||||
// << std::hex
|
||||
// << (UInt32)virtualCode
|
||||
// << " and HID usage code is 0x"
|
||||
// << usageCode
|
||||
// << ". First HID usage code is 0x"
|
||||
// << firstUsageCode
|
||||
// << std::dec
|
||||
// << "."
|
||||
// << std::endl;
|
||||
// }
|
||||
|
||||
if (myKeys[code] == 0) {
|
||||
|
||||
// Ok, everything went fine. Now we have a unique
|
||||
// corresponding sf::Keyboard::Key to one IOHIDElementRef.
|
||||
|
||||
myKeys[code] = aKey;
|
||||
|
||||
// And don't forget to keep the reference alive for our usage
|
||||
CFRetain(myKeys[code]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// These are known to be unbound :
|
||||
// Supposed Virtual | HID | Supposed Key
|
||||
// ===============================================
|
||||
// 0x1b | 0x2d | Hyphen
|
||||
// 0x39 | 0x39 | CapsLock
|
||||
// 0x47 | 0x53 | NumLock
|
||||
// 0x4c | 0x58 | Keypad Enter
|
||||
// 0x41 | 0x63 | Keypad Period
|
||||
// 0x6e | 0x65 | Application
|
||||
// 0x51 | 0x67 | Keypad Equal
|
||||
// 0x4c | 0x77 | Select
|
||||
|
||||
// else { // The key is unknown.
|
||||
// sf::Err() << "This is an unknow key. Virtual key code is 0x"
|
||||
// << std::hex
|
||||
// << (UInt32)virtualCode
|
||||
// << " and HID usage code is 0x"
|
||||
// << usageCode
|
||||
// << std::dec
|
||||
// << "."
|
||||
// << std::endl;
|
||||
// }
|
||||
|
||||
} /* if (error == noErr) */
|
||||
|
||||
else {
|
||||
sf::Err() << "Cannot translate the virtual key code, error : "
|
||||
<< error
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
} /* for (int i = 0; i < keysCount; ++i) */
|
||||
}
|
||||
|
||||
// Release unused stuff
|
||||
CFRelease(keys);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void HIDInputManager::LoadKey(IOHIDElementRef key)
|
||||
{
|
||||
// Get its virtual code
|
||||
UInt32 usageCode = IOHIDElementGetUsage(key);
|
||||
UInt8 virtualCode = UsageToVirtualCode(usageCode);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// At this point myKeys is filled with as many IOHIDElementRef as possible
|
||||
if (virtualCode == 0xff) {
|
||||
return; // no corresponding virtual code -> skip
|
||||
}
|
||||
|
||||
// Now translate the virtual code to unicode according to
|
||||
// the current keyboard layout
|
||||
|
||||
UInt32 deadKeyState = 0;
|
||||
// unicode string length is usually less or equal to 4
|
||||
UniCharCount maxStringLength = 4;
|
||||
UniCharCount actualStringLength = 0;
|
||||
UniChar unicodeString[maxStringLength];
|
||||
|
||||
OSStatus error;
|
||||
|
||||
error = UCKeyTranslate(myLayout, // current layout
|
||||
virtualCode, // our key
|
||||
kUCKeyActionDown, // or kUCKeyActionUp ?
|
||||
0x100, // no modifiers
|
||||
LMGetKbdType(), // keyboard's type
|
||||
kUCKeyTranslateNoDeadKeysBit,// some sort of option
|
||||
&deadKeyState, // unused stuff
|
||||
maxStringLength, // our memory limit
|
||||
&actualStringLength, // length of what we get
|
||||
unicodeString); // what we get
|
||||
|
||||
if (error == noErr) {
|
||||
// Translation went fine
|
||||
|
||||
// The corresponding SFML key code
|
||||
Keyboard::Key code = Keyboard::KeyCount; // KeyCound means 'none'
|
||||
|
||||
// First we look if the key down is from a list of characters
|
||||
// that depend on keyboard localization
|
||||
if (actualStringLength > 0) {
|
||||
code = LocalizedKeys(unicodeString[0]);
|
||||
}
|
||||
|
||||
// The key is not a localized one so we try to find a
|
||||
// corresponding code through virtual key code
|
||||
if (code == Keyboard::KeyCount) {
|
||||
code = NonLocalizedKeys(virtualCode);
|
||||
}
|
||||
|
||||
// A code was found, wonderful!
|
||||
if (code != Keyboard::KeyCount) {
|
||||
|
||||
// Ok, everything went fine. Now we have a unique
|
||||
// corresponding sf::Keyboard::Key to one IOHIDElementRef
|
||||
|
||||
myKeys[code].push_back(key);
|
||||
|
||||
// And don't forget to keep the reference alive for our usage
|
||||
CFRetain(myKeys[code].back());
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// These are known to be unbound :
|
||||
// Supposed Virtual | HID | Supposed Key
|
||||
// ===============================================
|
||||
// 0x1b | 0x2d | Hyphen
|
||||
// 0x39 | 0x39 | CapsLock
|
||||
// 0x47 | 0x53 | NumLock
|
||||
// 0x4c | 0x58 | Keypad Enter
|
||||
// 0x41 | 0x63 | Keypad Period
|
||||
// 0x6e | 0x65 | Application
|
||||
// 0x51 | 0x67 | Keypad Equal
|
||||
// 0x4c | 0x77 | Select
|
||||
|
||||
//if (code == Keyboard::KeyCount) { // The key is unknown.
|
||||
// sf::Err() << "This is an unknow key. Virtual key code is 0x"
|
||||
// << std::hex
|
||||
// << (UInt32)virtualCode
|
||||
// << " and HID usage code is 0x"
|
||||
// << usageCode
|
||||
// << std::dec
|
||||
// << "."
|
||||
// << std::endl;
|
||||
//}
|
||||
|
||||
} /* if (error == noErr) */
|
||||
else {
|
||||
|
||||
sf::Err() << "Cannot translate the virtual key code, error : "
|
||||
<< error
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -376,9 +340,10 @@ void HIDInputManager::FreeUp()
|
||||
if (myManager != 0) CFRelease(myManager);
|
||||
|
||||
for (unsigned int i = 0; i < Keyboard::KeyCount; ++i) {
|
||||
if (myKeys[i] != 0) {
|
||||
CFRelease(myKeys[i]);
|
||||
for (IOHIDElements::iterator it = myKeys[i].begin(); it != myKeys[i].end(); ++it) {
|
||||
CFRelease(*it);
|
||||
}
|
||||
myKeys[i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,13 +368,44 @@ CFDictionaryRef HIDInputManager::CopyDevicesMaskForManager(UInt32 page, UInt32 u
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
CFSetRef HIDInputManager::CopyDevices(UInt32 page, UInt32 usage)
|
||||
{
|
||||
// Filter and keep only the requested devices
|
||||
CFDictionaryRef mask = CopyDevicesMaskForManager(page, usage);
|
||||
|
||||
IOHIDManagerSetDeviceMatching(myManager, mask);
|
||||
|
||||
CFRelease(mask);
|
||||
mask = 0;
|
||||
|
||||
CFSetRef devices = IOHIDManagerCopyDevices(myManager);
|
||||
if (devices == NULL) {
|
||||
sf::Err() << "Cannot find any devices." << std::endl;
|
||||
FreeUp();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Is there at least one keyboard ?
|
||||
CFIndex deviceCount = CFSetGetCount(devices);
|
||||
if (deviceCount < 1) {
|
||||
sf::Err() << "Found no device." << std::endl;
|
||||
CFRelease(devices);
|
||||
FreeUp();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
UInt8 HIDInputManager::UsageToVirtualCode(UInt32 usage)
|
||||
{
|
||||
// Some usage key doesn't have any corresponding virtual code or it was not
|
||||
// found.
|
||||
// Some usage key doesn't have any corresponding virtual
|
||||
// code or it was not found (return 0xff).
|
||||
switch (usage) {
|
||||
case kHIDUsage_KeyboardErrorRollOver: return 0xff;
|
||||
case kHIDUsage_KeyboardPOSTFail: return 0xff;
|
||||
|
Loading…
Reference in New Issue
Block a user