2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
26 ===========================================================================
30 #import "../../idlib/precompiled.h"
32 #import "macosx_local.h"
33 #import "macosx_sys.h"
35 #import <AppKit/NSCursor.h>
36 #import <AppKit/NSWindow.h>
37 #import <AppKit/NSScreen.h>
38 #import <AppKit/NSApplication.h>
39 #import <AppKit/NSGraphicsContext.h>
40 #import <AppKit/NSEvent.h>
42 #import <Foundation/NSArray.h>
43 #import <Foundation/NSString.h>
44 #import <Foundation/NSRunLoop.h>
45 #import <Carbon/Carbon.h>
47 #import <ApplicationServices/ApplicationServices.h>
54 static NSDate *distantPast = NULL;
55 static bool inputActive = false;
56 static bool mouseActive = false;
57 static bool inputRectValid = NO;
58 static CGRect inputRect;
59 static const void *sKLuchrData = NULL;
60 static const void *sKLKCHRData = NULL;
62 int vkeyToDoom3Key[256] = {
63 /*0x00*/ 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x',
64 /*0x08*/ 'c', 'v', '?', 'b', 'q', 'w', 'e', 'r',
65 /*0x10*/ 'y', 't', '1', '2', '3', '4', '6', '5',
66 /*0x18*/ '=', '9', '7', '-', '8', '0', ']', 'o',
67 /*0x20*/ 'u', '[', 'i', 'p', K_ENTER, 'l', 'j', '\'',
68 /*0x28*/ 'k', ';', '\\', ',', '/', 'n', 'm', '.',
69 /*0x30*/ K_TAB, K_SPACE, '`', K_BACKSPACE, '?', K_ESCAPE, '?', K_COMMAND,
70 /*0x38*/ K_SHIFT, K_CAPSLOCK, K_ALT, K_CTRL, '?', '?', '?', '?',
71 /*0x40*/ '?', K_KP_DEL, '?', K_KP_STAR, '?', K_KP_PLUS, '?', K_KP_NUMLOCK,
72 /*0x48*/ '?', '?', '?', K_KP_SLASH, K_KP_ENTER, '?', K_KP_MINUS, '?',
73 /*0x50*/ '?', K_KP_EQUALS, K_KP_INS, K_KP_END, K_KP_DOWNARROW, K_KP_PGDN, K_KP_LEFTARROW, K_KP_5,
74 /*0x58*/ K_KP_RIGHTARROW, K_KP_HOME, '?', K_KP_UPARROW, K_KP_PGUP, '?', '?', '?',
75 /*0x60*/ K_F5, K_F6, K_F7, K_F3, K_F8, K_F9, '?', K_F11,
76 /*0x68*/ '?', K_PRINT_SCR, '?', K_F14, '?', K_F10, '?', K_F12,
77 /*0x70*/ '?', K_F15, K_INS, K_HOME, K_PGUP, K_DEL, K_F4, K_END,
78 /*0x78*/ K_F2, K_PGDN, K_F1, K_LEFTARROW, K_RIGHTARROW, K_DOWNARROW, K_UPARROW, K_POWER
81 int vkeyToDoom3Key_French[256] = {
82 /*0x00*/ 'q', 's', 'd', 'f', 'h', 'g', 'w', 'x',
83 /*0x08*/ 'c', 'v', '?', 'b', 'a', 'z', 'e', 'r',
84 /*0x10*/ 'y', 't', '1', '2', '3', '4', '6', '5',
85 /*0x18*/ '-', '9', '7', ')', '8', '0', '$', 'o',
86 /*0x20*/ 'u', '^', 'i', 'p', K_ENTER, 'l', 'j', 'ù',
87 /*0x28*/ 'k', 'm', 0x60, ';', '=', 'n', ',', ':',
88 /*0x30*/ K_TAB, K_SPACE, '<', K_BACKSPACE, '?', K_ESCAPE, '?', K_COMMAND,
89 /*0x38*/ K_SHIFT, K_CAPSLOCK, K_ALT, K_CTRL, '?', '?', '?', '?',
90 /*0x40*/ '?', K_KP_DEL, '?', K_KP_STAR, '?', K_KP_PLUS, '?', K_KP_NUMLOCK,
91 /*0x48*/ '?', '?', '?', K_KP_SLASH, K_KP_ENTER, '?', K_KP_MINUS, '?',
92 /*0x50*/ '?', K_KP_EQUALS, K_KP_INS, K_KP_END, K_KP_DOWNARROW, K_KP_PGDN, K_KP_LEFTARROW, K_KP_5,
93 /*0x58*/ K_KP_RIGHTARROW, K_KP_HOME, '?', K_KP_UPARROW, K_KP_PGUP, '?', '?', '?',
94 /*0x60*/ K_F5, K_F6, K_F7, K_F3, K_F8, K_F9, '?', K_F11,
95 /*0x68*/ '?', K_PRINT_SCR, '?', K_F14, '?', K_F10, '?', K_F12,
96 /*0x70*/ '?', K_F15, K_INS, K_HOME, K_PGUP, K_DEL, K_F4, K_END,
97 /*0x78*/ K_F2, K_PGDN, K_F1, K_LEFTARROW, K_RIGHTARROW, K_DOWNARROW, K_UPARROW, K_POWER
100 int vkeyToDoom3Key_German[256] = {
101 /*0x00*/ 'a', 's', 'd', 'f', 'h', 'g', 'y', 'x',
102 /*0x08*/ 'c', 'v', '?', 'b', 'q', 'w', 'e', 'r',
103 /*0x10*/ 'z', 't', '1', '2', '3', '4', '6', '5',
104 /*0x18*/ '«', '9', '7', '-', '8', '0', '+', 'o',
105 /*0x20*/ 'u', '[', 'i', 'p', K_ENTER, 'l', 'j', '\'',
106 /*0x28*/ 'k', ';', '#', ',', '-', 'n', 'm', '.',
107 /*0x30*/ K_TAB, K_SPACE, '`', K_BACKSPACE, '?', K_ESCAPE, '?', K_COMMAND,
108 /*0x38*/ K_SHIFT, K_CAPSLOCK, K_ALT, K_CTRL, '?', '?', '?', '?',
109 /*0x40*/ '?', K_KP_DEL, '?', K_KP_STAR, '?', K_KP_PLUS, '?', K_KP_NUMLOCK,
110 /*0x48*/ '?', '?', '?', K_KP_SLASH, K_KP_ENTER, '?', K_KP_MINUS, '?',
111 /*0x50*/ '?', K_KP_EQUALS, K_KP_INS, K_KP_END, K_KP_DOWNARROW, K_KP_PGDN, K_KP_LEFTARROW, K_KP_5,
112 /*0x58*/ K_KP_RIGHTARROW, K_KP_HOME, '?', K_KP_UPARROW, K_KP_PGUP, '?', '?', '?',
113 /*0x60*/ K_F5, K_F6, K_F7, K_F3, K_F8, K_F9, '?', K_F11,
114 /*0x68*/ '?', K_PRINT_SCR, '?', K_F14, '?', K_F10, '?', K_F12,
115 /*0x70*/ '?', K_F15, K_INS, K_HOME, K_PGUP, K_DEL, K_F4, K_END,
116 /*0x78*/ K_F2, K_PGDN, K_F1, K_LEFTARROW, K_RIGHTARROW, K_DOWNARROW, K_UPARROW, K_POWER
119 static const int *vkeyTable = vkeyToDoom3Key;
126 void Sys_InitScanTable( void ) {
127 KeyboardLayoutRef kbLayout;
129 idStr lang = cvarSystem->GetCVarString( "sys_lang" );
130 if ( lang.Length() == 0 ) {
134 if ( lang.Icmp( "english" ) == 0 ) {
135 vkeyTable = vkeyToDoom3Key;
136 } else if ( lang.Icmp( "french" ) == 0 ) {
137 vkeyTable = vkeyToDoom3Key_French;
138 } else if ( lang.Icmp( "german" ) == 0 ) {
139 vkeyTable = vkeyToDoom3Key_German;
142 if ( KLGetCurrentKeyboardLayout( &kbLayout ) == 0 ) {
143 if ( KLGetKeyboardLayoutProperty( kbLayout, kKLuchrData, &sKLuchrData ) ) {
144 common->Warning("KLGetKeyboardLayoutProperty failed");
146 if ( !sKLuchrData ) {
147 if ( KLGetKeyboardLayoutProperty( kbLayout, kKLKCHRData, &sKLKCHRData ) ) {
148 common->Warning("KLGetKeyboardLayoutProperty failed");
152 if ( !sKLuchrData && !sKLKCHRData ) {
153 common->Warning("Keyboard input initialziation failed");
157 void Sys_InitInput( void ) {
158 common->Printf( "------- Input Initialization -------\n" );
160 if ( !distantPast ) {
161 distantPast = [ [ NSDate distantPast ] retain ];
169 void Sys_ShutdownInput( void ) {
170 common->Printf( "------- Input Shutdown -------\n" );
172 if ( !inputActive ) {
177 IN_DeactivateMouse();
180 common->Printf( "------------------------------\n" );
183 void processMouseMovedEvent( NSEvent *mouseMovedEvent ) {
186 if ( !mouseActive ) {
192 #define ACT_LIKE_WINDOWS
193 #ifdef ACT_LIKE_WINDOWS
194 cvar_t *in_mouseLowEndSlope = Cvar_Get("in_mouseLowEndSlope", "3.5", CVAR_ARCHIVE);
195 if (in_mouseLowEndSlope->value < 1) {
196 Cvar_Set("in_mouseLowEndSlope", "1");
199 cvar_t *in_mouseLowEndSlope = Cvar_Get("in_mouseLowEndSlope", "1", CVAR_ARCHIVE);
200 if (in_mouseLowEndSlope->value < 1) {
201 Cvar_Set("in_mouseLowEndSlope", "1");
205 cvar_t *in_mouseHighEndCutoff = Cvar_Get("in_mouseHighEndCutoff", "20", CVAR_ARCHIVE);
206 if (in_mouseLowEndSlope->value < 1) {
207 Cvar_Set("in_mouseHighEndCutoff", "1");
212 CGGetLastMouseDelta(&dx, &dy);
215 #if 0 // this is be handled by the mouse driver clean me out later
216 CGMouseDelta distSqr;
219 distSqr = dx * dx + dy * dy;
220 //Com_Printf("distSqr = %d\n", distSqr);
222 /* This code is here to help people that like the feel of the Logitech USB Gaming Mouse with the Win98 drivers. By empirical testing, the Windows drivers seem to be more heavily accelerated at the low end of the curve. */
223 //N = in_mouseHighEndCutoff->value;
227 float dist, accel, scale;
229 //m0 = in_mouseLowEndSlope->value;
231 dist = sqrt(distSqr);
232 accel = (((m0 - 1.0)/(N*N) * dist + (2.0 - 2.0*m0)/N) * dist + m0) * dist;
234 scale = accel / dist;
235 //Com_Printf("dx = %d, dy = %d, dist = %f, accel = %f, scale = %f\n", dx, dy, dist, accel, scale);
241 Posix_QueEvent( SE_MOUSE, dx, dy, 0, NULL );
242 Posix_AddMousePollEvent( M_DELTAX, dx );
243 Posix_AddMousePollEvent( M_DELTAY, dy );
247 inline bool OSX_LookupCharacter(unsigned short vkey, unsigned int modifiers, bool keyDownFlag, unsigned char *outChar)
250 UInt32 deadKeyState = 0;
251 UniChar unicodeString[16];
252 UniCharCount actualStringLength = 0;
253 static UInt32 keyTranslateState = 0;
255 // Only want character if Translate() returns a single character
257 UCKeyTranslate( (UCKeyboardLayout*)sKLuchrData, vkey, keyDownFlag ? kUCKeyActionDown : kUCKeyActionUp, modifiers,
258 LMGetKbdType(), 0, &deadKeyState, 16, &actualStringLength, unicodeString );
260 if ( actualStringLength == 1 ) {
261 *outChar = (unsigned char)unicodeString[0];
265 else if ( sKLKCHRData ) {
266 translated = KeyTranslate( sKLKCHRData, vkey, &keyTranslateState );
267 if ( ( translated & 0x00ff0000 ) == 0 ) {
268 *outChar = translated & 0xff;
275 void OSX_ProcessKeyEvent( NSEvent *keyEvent, bool keyDownFlag ) {
276 unsigned char character;
277 unsigned int modifiers = 0;
278 unsigned short vkey = [ keyEvent keyCode ];
280 if ( [ keyEvent modifierFlags ] & NSAlphaShiftKeyMask )
281 modifiers |= alphaLock;
282 if ( [ keyEvent modifierFlags ] & NSShiftKeyMask )
283 modifiers |= shiftKey;
284 if ( [ keyEvent modifierFlags ] & NSControlKeyMask )
285 modifiers |= controlKey;
286 if ( [ keyEvent modifierFlags ] & NSAlternateKeyMask )
287 modifiers |= optionKey;
288 if ( [ keyEvent modifierFlags ] & NSCommandKeyMask )
292 int doomKey = (unsigned char)vkeyTable[vkey];
293 Posix_QueEvent( SE_KEY, doomKey, keyDownFlag, 0, NULL );
295 if ( OSX_LookupCharacter(vkey, modifiers, keyDownFlag, &character ) &&
296 character != Sys_GetConsoleKey( false ) && character != Sys_GetConsoleKey( true ) ) {
297 Posix_QueEvent( SE_CHAR, character, 0, 0, NULL);
300 Posix_AddKeyboardPollEvent( doomKey, keyDownFlag );
305 void sendEventForMaskChangeInFlags( int quakeKey, unsigned int modifierMask, unsigned int oldModifierFlags, unsigned int newModifierFlags ) {
306 bool oldHadModifier, newHasModifier;
308 oldHadModifier = (oldModifierFlags & modifierMask) != 0;
309 newHasModifier = (newModifierFlags & modifierMask) != 0;
310 if (oldHadModifier != newHasModifier) {
311 //NSLog(@"Key %d posted for modifier mask modifierMask", quakeKey);
312 Posix_QueEvent( SE_KEY, quakeKey, newHasModifier, 0, NULL);
313 Posix_AddKeyboardPollEvent( quakeKey, newHasModifier );
317 void processFlagsChangedEvent( NSEvent *flagsChangedEvent ) {
318 static int oldModifierFlags;
319 int newModifierFlags;
321 newModifierFlags = [flagsChangedEvent modifierFlags];
322 sendEventForMaskChangeInFlags( K_ALT, NSAlternateKeyMask, oldModifierFlags, newModifierFlags );
323 sendEventForMaskChangeInFlags( K_CTRL, NSControlKeyMask, oldModifierFlags, newModifierFlags );
324 sendEventForMaskChangeInFlags( K_SHIFT, NSShiftKeyMask, oldModifierFlags, newModifierFlags );
325 oldModifierFlags = newModifierFlags;
328 void processSystemDefinedEvent( NSEvent *systemDefinedEvent ) {
329 static int oldButtons = 0;
334 if ( [systemDefinedEvent subtype] == 7 ) {
336 if ( !mouseActive ) {
340 buttons = [systemDefinedEvent data2];
341 buttonsDelta = oldButtons ^ buttons;
343 //common->Printf( "uberbuttons: %08lx %08lx\n", buttonsDelta, buttons );
345 if (buttonsDelta & 1) {
346 isDown = buttons & 1;
347 Posix_QueEvent( SE_KEY, K_MOUSE1, isDown, 0, NULL);
348 Posix_AddMousePollEvent( M_ACTION1, isDown );
351 if (buttonsDelta & 2) {
352 isDown = buttons & 2;
353 Posix_QueEvent( SE_KEY, K_MOUSE2, isDown, 0, NULL);
354 Posix_AddMousePollEvent( M_ACTION2, isDown );
357 if (buttonsDelta & 4) {
358 isDown = buttons & 4;
359 Posix_QueEvent( SE_KEY, K_MOUSE3, isDown, 0, NULL);
360 Posix_AddMousePollEvent( M_ACTION3, isDown );
363 if (buttonsDelta & 8) {
364 isDown = buttons & 8;
365 Posix_QueEvent( SE_KEY, K_MOUSE4, isDown, 0, NULL);
366 Posix_AddMousePollEvent( M_ACTION4, isDown );
369 if (buttonsDelta & 16) {
370 isDown = buttons & 16;
371 Posix_QueEvent( SE_KEY, K_MOUSE5, isDown, 0, NULL);
372 Posix_AddMousePollEvent( M_ACTION5, isDown );
375 oldButtons = buttons;
379 void processEvent( NSEvent *event ) {
380 NSEventType eventType;
382 if ( !inputActive ) {
386 eventType = [ event type ];
388 switch ( eventType ) {
389 // These four event types are ignored since we do all of our mouse down/up process via the uber-mouse system defined event. We have to accept these events however since they get enqueued and the queue will fill up if we don't.
390 case NSLeftMouseDown:
392 case NSRightMouseDown:
394 //NSLog( @"ignore simple mouse event %@", event );
397 case NSLeftMouseDragged:
398 case NSRightMouseDragged:
399 processMouseMovedEvent( event );
402 // Send ALL command key-ups to Quake, but not command key-downs, otherwise if the user hits a key, presses command, and lets up on the key, the key-up won't register.
403 if ( [ event modifierFlags ] & NSCommandKeyMask ) {
404 NSLog( @"command key up ignored: %@", event );
408 OSX_ProcessKeyEvent( event, eventType == NSKeyDown );
411 processFlagsChangedEvent( event );
413 case NSSystemDefined:
414 processSystemDefinedEvent( event );
417 if ([event deltaY] < 0.0) {
418 Posix_QueEvent( SE_KEY, K_MWHEELDOWN, true, 0, NULL );
419 Posix_QueEvent( SE_KEY, K_MWHEELDOWN, false, 0, NULL );
420 Posix_AddMousePollEvent( M_DELTAZ, -1 );
422 Posix_QueEvent( SE_KEY, K_MWHEELUP, true, 0, NULL );
423 Posix_QueEvent( SE_KEY, K_MWHEELUP, false, 0, NULL );
424 Posix_AddMousePollEvent( M_DELTAZ, 1 );
428 //NSLog( @"handle event %@", event );
431 [NSApp sendEvent:event];
434 void Posix_PollInput( void ) {
436 unsigned int eventMask;
438 eventMask = NSAnyEventMask;
440 while ( ( event = [ NSApp nextEventMatchingMask: eventMask
441 untilDate: distantPast
442 inMode: NSDefaultRunLoopMode
444 processEvent( event );
448 void Sys_PreventMouseMovement( CGPoint point ) {
451 //common->Printf( "**** Calling CGAssociateMouseAndMouseCursorPosition(false)\n" );
452 err = CGAssociateMouseAndMouseCursorPosition( false );
453 if ( err != CGEventNoErr ) {
454 common->Error( "Could not disable mouse movement, CGAssociateMouseAndMouseCursorPosition returned %d\n", err );
457 // Put the mouse in the position we want to leave it at
458 err = CGWarpMouseCursorPosition( point );
459 if ( err != CGEventNoErr ) {
460 common->Error( "Could not disable mouse movement, CGWarpMouseCursorPosition returned %d\n", err );
464 void Sys_ReenableMouseMovement() {
467 //common->Printf( "**** Calling CGAssociateMouseAndMouseCursorPosition(true)\n" );
468 err = CGAssociateMouseAndMouseCursorPosition( true );
469 if ( err != CGEventNoErr ) {
470 common->Error( "Could not reenable mouse movement, CGAssociateMouseAndMouseCursorPosition returned %d\n", err );
473 // Leave the mouse where it was -- don't warp here.
476 void Sys_LockMouseInInputRect(CGRect rect) {
479 center.x = rect.origin.x + rect.size.width / 2.0;
480 center.y = rect.origin.y + rect.size.height / 2.0;
482 // Now, put the mouse in the middle of the input rect (anywhere over it would do)
483 // and don't allow it to move. This means that the user won't be able to accidentally
484 // select another application.
485 Sys_PreventMouseMovement(center);
488 void Sys_SetMouseInputRect(CGRect newRect) {
489 inputRectValid = YES;
493 Sys_LockMouseInInputRect( inputRect );
497 void IN_ActivateMouse( void ) {
501 if ( inputRectValid ) {
502 // Make sure that if window moved we don't hose the user...
503 Sys_UpdateWindowMouseInputRect();
505 Sys_LockMouseInInputRect( inputRect );
506 CGDisplayHideCursor( Sys_DisplayToUse() );
510 void IN_DeactivateMouse( void ) {
511 if ( !mouseActive ) {
514 Sys_ReenableMouseMovement();
515 CGDisplayShowCursor( Sys_DisplayToUse() );
524 unsigned char Sys_MapCharForKey( int key ) {
525 return (unsigned char)key;
533 unsigned char Sys_GetConsoleKey( bool shifted ) {
534 if ( vkeyTable == vkeyToDoom3Key_French ) {
535 return shifted ? '>' : '<';
538 return shifted ? '~' : '`';