--- src/mac/CocoaKeyboard.mm (revision 0) +++ src/mac/CocoaKeyboard.mm (revision 0) @@ -0,0 +1,433 @@ +/* + The zlib/libpng License + + Copyright (c) 2005-2007 Phillip Castaneda (pjcast -- www.wreckedgames.com) + + This software is provided 'as-is', without any express or implied warranty. In no event will + the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following + restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that + you wrote the original software. If you use this software in a product, + an acknowledgment in the product documentation would be appreciated but is + not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + */ + +#include "mac/CocoaKeyboard.h" +#include "mac/CocoaInputManager.h" +#include "mac/CocoaHelpers.h" +#include "OISException.h" +#include "OISEvents.h" + +#include + +#include +#include +#include + +using namespace OIS; + +//-------------------------------------------------------------------// +CocoaKeyboard::CocoaKeyboard( InputManager* creator, bool buffered, bool repeat ) + : Keyboard(creator->inputSystemName(), buffered, 0, creator) +{ + CocoaInputManager *man = static_cast(mCreator); + mResponder = [[CocoaKeyboardView alloc] init]; + if(!mResponder) + OIS_EXCEPT( E_General, "CocoaKeyboardView::CocoaKeyboardView >> Error creating event responder" ); + + [man->_getWindow() makeFirstResponder:mResponder]; + [mResponder setUseRepeat:repeat]; + [mResponder setOISKeyboardObj:this]; + + static_cast(mCreator)->_setKeyboardUsed(true); +} + +//-------------------------------------------------------------------// +CocoaKeyboard::~CocoaKeyboard() +{ + if (mResponder) + { + [mResponder release]; + mResponder = nil; + } + + // Free the input managers keyboard + static_cast(mCreator)->_setKeyboardUsed(false); +} + +//-------------------------------------------------------------------// +void CocoaKeyboard::_initialize() +{ + mModifiers = 0; +} + +//-------------------------------------------------------------------// +bool CocoaKeyboard::isKeyDown( KeyCode key ) const +{ + return [mResponder isKeyDown:key]; +} + +//-------------------------------------------------------------------// +void CocoaKeyboard::capture() +{ + [mResponder capture]; +} + +//-------------------------------------------------------------------// +std::string& CocoaKeyboard::getAsString( KeyCode key ) +{ + getString = ""; + + CGKeyCode deviceKeycode; + + // Convert OIS KeyCode back into device keycode + VirtualtoOIS_KeyMap keyMap = [mResponder keyConversionMap]; + for(VirtualtoOIS_KeyMap::iterator it = keyMap.begin(); it != keyMap.end(); ++it) + { + if(it->second == key) + deviceKeycode = it->first; + } + + UniChar unicodeString[1]; + UniCharCount actualStringLength; + + CGEventSourceRef sref = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ref = CGEventCreateKeyboardEvent(sref, deviceKeycode, true); + CGEventKeyboardGetUnicodeString(ref, sizeof(unicodeString) / sizeof(*unicodeString), &actualStringLength, unicodeString); + getString = unicodeString[0]; + + return getString; +} + +//-------------------------------------------------------------------// +void CocoaKeyboard::setBuffered( bool buffered ) +{ + mBuffered = buffered; +} + +//-------------------------------------------------------------------// +void CocoaKeyboard::copyKeyStates( char keys[256] ) const +{ + [mResponder copyKeyStates:keys]; +} + +@implementation CocoaKeyboardView + +- (id)init +{ + self = [super init]; + if (self) { + [self populateKeyConversion]; + memset( &KeyBuffer, 0, 256 ); + prevModMask = 0; + } + return self; +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (BOOL)canBecomeKeyView +{ + return YES; +} + +- (void)setOISKeyboardObj:(CocoaKeyboard *)obj +{ + oisKeyboardObj = obj; +} + +- (void)capture +{ + // If not buffered just return, we update the unbuffered automatically + if ( !oisKeyboardObj->buffered() && !oisKeyboardObj->getEventCallback() ) + return; + + // Run through our event stack + eventStack::iterator cur_it; + + for (cur_it = pendingEvents.begin(); cur_it != pendingEvents.end(); cur_it++) + { + if ( (*cur_it).type() == MAC_KEYDOWN || (*cur_it).type() == MAC_KEYREPEAT) + oisKeyboardObj->getEventCallback()->keyPressed( (*cur_it).event() ); + else if ( (*cur_it).type() == MAC_KEYUP ) + oisKeyboardObj->getEventCallback()->keyReleased( (*cur_it).event() ); + } + + pendingEvents.clear(); +} + +- (void)setUseRepeat:(bool)repeat +{ + useRepeat = repeat; +} + +- (bool)isKeyDown:(KeyCode)key +{ + return KeyBuffer[key]; +} + +- (void)copyKeyStates:(char [256])keys +{ + memcpy( keys, KeyBuffer, 256 ); +} + +- (void)populateKeyConversion +{ + // Virtual Key Map to KeyCode + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x12, KC_1)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x13, KC_2)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x14, KC_3)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x15, KC_4)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x17, KC_5)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x16, KC_6)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x1A, KC_7)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x1C, KC_8)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x19, KC_9)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x1D, KC_0)); + + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x33, KC_BACK)); // might be wrong + + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x1B, KC_MINUS)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x18, KC_EQUALS)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x31, KC_SPACE)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x2B, KC_COMMA)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x2F, KC_PERIOD)); + + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x2A, KC_BACKSLASH)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x2C, KC_SLASH)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x21, KC_LBRACKET)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x1E, KC_RBRACKET)); + + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x35, KC_ESCAPE)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x39, KC_CAPITAL)); + + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x30, KC_TAB)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x24, KC_RETURN)); // double check return/enter + + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_colon, KC_COLON)); // no colon? + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x29, KC_SEMICOLON)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x27, KC_APOSTROPHE)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x32, KC_GRAVE)); + + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x0B, KC_B)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x00, KC_A)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x08, KC_C)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x02, KC_D)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x0E, KC_E)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x03, KC_F)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x05, KC_G)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x04, KC_H)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x22, KC_I)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x26, KC_J)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x28, KC_K)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x25, KC_L)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x2E, KC_M)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x2D, KC_N)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x1F, KC_O)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x23, KC_P)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x0C, KC_Q)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x0F, KC_R)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x01, KC_S)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x11, KC_T)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x20, KC_U)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x09, KC_V)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x0D, KC_W)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x07, KC_X)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x10, KC_Y)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x06, KC_Z)); + + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x7A, KC_F1)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x78, KC_F2)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x63, KC_F3)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x76, KC_F4)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x60, KC_F5)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x61, KC_F6)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x62, KC_F7)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x64, KC_F8)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x65, KC_F9)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x6D, KC_F10)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x67, KC_F11)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x6F, KC_F12)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x69, KC_F13)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x6B, KC_F14)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x71, KC_F15)); + + // Keypad + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x52, KC_NUMPAD0)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x53, KC_NUMPAD1)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x54, KC_NUMPAD2)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x55, KC_NUMPAD3)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x56, KC_NUMPAD4)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x57, KC_NUMPAD5)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x58, KC_NUMPAD6)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x59, KC_NUMPAD7)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x5B, KC_NUMPAD8)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x5C, KC_NUMPAD9)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x45, KC_ADD)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x4E, KC_SUBTRACT)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x41, KC_DECIMAL)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x51, KC_NUMPADEQUALS)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x4B, KC_DIVIDE)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x43, KC_MULTIPLY)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x4C, KC_NUMPADENTER)); + + // Keypad with numlock off + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x73, KC_NUMPAD7)); // not sure of these + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_KP_Up, KC_NUMPAD8)); // check on a non-laptop + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_KP_Page_Up, KC_NUMPAD9)); + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_KP_Left, KC_NUMPAD4)); + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_KP_Begin, KC_NUMPAD5)); + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_KP_Right, KC_NUMPAD6)); + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_KP_End, KC_NUMPAD1)); + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_KP_Down, KC_NUMPAD2)); + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_KP_Page_Down, KC_NUMPAD3)); + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_KP_Insert, KC_NUMPAD0)); + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_KP_Delete, KC_DECIMAL)); + + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x7E, KC_UP)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x7D, KC_DOWN)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x7B, KC_LEFT)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x7C, KC_RIGHT)); + + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x74, KC_PGUP)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x79, KC_PGDOWN)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x73, KC_HOME)); + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x77, KC_END)); + + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_Print, KC_SYSRQ)); // ?? + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_Scroll_Lock, KC_SCROLL)); // ?? + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_Pause, KC_PAUSE)); // ?? + + + //keyConversion.insert(VirtualtoOIS_KeyMap::value_type(XK_Insert, KC_INSERT)); // ?? + keyConversion.insert(VirtualtoOIS_KeyMap::value_type(0x75, KC_DELETE)); // del under help key? +} + +- (void)injectEvent:(KeyCode)kc eventTime:(unsigned int)time eventType:(MacEventType)type +{ + [self injectEvent:kc eventTime:time eventType:type eventText:0]; +} + +- (void)injectEvent:(KeyCode)kc eventTime:(unsigned int)time eventType:(MacEventType)type eventText:(unsigned int)txt +{ + // set to 1 if this is either a keydown or repeat + KeyBuffer[kc] = ( type == MAC_KEYUP ) ? 0 : 1; + + if ( oisKeyboardObj->buffered() && oisKeyboardObj->getEventCallback() ) + pendingEvents.push_back( CocoaKeyStackEvent( KeyEvent(oisKeyboardObj, kc, txt), type) ); +} + +#pragma mark Key Event overrides +- (void)keyDown:(NSEvent *)theEvent +{ + unsigned short virtualKey = [theEvent keyCode]; + unsigned int time = (unsigned int)[theEvent timestamp]; + KeyCode kc = keyConversion[virtualKey]; + + // Record what kind of text we should pass the KeyEvent + unichar text[10]; + char macChar; + if (oisKeyboardObj->getTextTranslation() == OIS::Keyboard::Unicode) + { + // Get string size + NSUInteger stringsize = [[theEvent charactersIgnoringModifiers] length]; + [[theEvent charactersIgnoringModifiers] getCharacters:text range:NSMakeRange(0, stringsize)]; +// NSLog(@"Characters: %ls", text); +// std::cout << "String length: " << stringsize << std::endl; + + if(stringsize > 0) + { + // For each unicode char, send an event + for ( unsigned int i = 0; i < stringsize; i++ ) + { + [self injectEvent:kc eventTime:time eventType:MAC_KEYDOWN eventText:(unsigned int)text[i]]; + } + } + } + else if (oisKeyboardObj->getTextTranslation() == OIS::Keyboard::Ascii) + { + macChar = [[theEvent charactersIgnoringModifiers] characterAtIndex:0]; + [self injectEvent:kc eventTime:time eventType:MAC_KEYDOWN eventText:(unsigned int)macChar]; + } + else + { + [self injectEvent:kc eventTime:time eventType:MAC_KEYDOWN]; + } +} + +- (void)keyUp:(NSEvent *)theEvent +{ + unsigned short virtualKey = [theEvent keyCode]; + + KeyCode kc = keyConversion[virtualKey]; + [self injectEvent:kc eventTime:[theEvent timestamp] eventType:MAC_KEYUP]; +} + +- (void)flagsChanged:(NSEvent *)theEvent +{ + NSUInteger mods = [theEvent modifierFlags]; + + // Find the changed bit + NSUInteger change = prevModMask ^ mods; + MacEventType newstate = ((change & prevModMask) > 0) ? MAC_KEYUP : MAC_KEYDOWN; + unsigned int time = (unsigned int)[theEvent timestamp]; + + //cout << "preMask: " << hex << prevModMask << endl; + //cout << "ModMask: " << hex << mods << endl; + //cout << "Change: " << hex << (change & prevModMask) << endl << endl; + + // TODO test modifiers on a full keyboard to check if different mask for left/right + switch (change) + { + case (NSShiftKeyMask): // shift + oisKeyboardObj->_getModifiers() &= (newstate == MAC_KEYDOWN) ? OIS::Keyboard::Shift : ~OIS::Keyboard::Shift; + [self injectEvent:KC_LSHIFT eventTime:time eventType:newstate]; + break; + + case (NSAlternateKeyMask): // option (alt) + oisKeyboardObj->_getModifiers() &= (newstate == MAC_KEYDOWN) ? OIS::Keyboard::Alt : -OIS::Keyboard::Alt; + [self injectEvent:KC_LMENU eventTime:time eventType:newstate]; + break; + + case (NSControlKeyMask): // Ctrl + oisKeyboardObj->_getModifiers() += (newstate == MAC_KEYDOWN) ? OIS::Keyboard::Ctrl : -OIS::Keyboard::Ctrl; + [self injectEvent:KC_LCONTROL eventTime:time eventType:newstate]; + break; + + case (NSCommandKeyMask): // apple + [self injectEvent:KC_LWIN eventTime:time eventType:newstate]; + break; + + case (NSFunctionKeyMask): // fn key + [self injectEvent:KC_APPS eventTime:time eventType:newstate]; + break; + + case (NSAlphaShiftKeyMask): // caps lock + [self injectEvent:KC_CAPITAL eventTime:time eventType:newstate]; + break; + } + + if([theEvent keyCode] == NSClearLineFunctionKey) // numlock + [self injectEvent:KC_NUMLOCK eventTime:time eventType:newstate]; + + prevModMask = mods; +} + +- (VirtualtoOIS_KeyMap)keyConversionMap +{ + return keyConversion; +} + +@end