src/video/quartz/SDL_QuartzEvents.m
author Sam Lantinga <slouken@libsdl.org>
Tue, 11 Sep 2001 19:00:18 +0000
changeset 172 37e3ca9254c7
parent 168 e92aa316c517
child 252 e8157fcb3114
permissions -rw-r--r--
Date: Sat, 8 Sep 2001 04:42:23 +0200 From: Max Horn <max@quendi.de> Subject: SDL/OSX: Joystick; Better key handling I just finished implementing improved keyhandling for OS X (in fact the code should be easily ported to the "normal" MacOS part of SDL, I just had no chance yet). Works like this: First init the mapping table statically like before. Them, it queries the OS for the "official" key table, then iterates over all 127 scancode and gets the associates ascii code. It ignores everythng below 32 (has to, as it would lead to many problems if we did not... e.g. both ESC and NUM LOCk produce an ascii code 27 on my keyboard), and all stuff above 127 is mapped to SDLK_WORLD_* simply in the order it is encountered. In addition, caps lock is now working, too. The code work flawless for me, but since I only have one keyboard, I may have not encountered some serious problem... but I am pretty confident that it is better than the old code in most cases. The joystick driver works fine for me, too. I think it can be added to CVS already. It would simply be helpful if more people would test it. Hm, I wonder if Maelstrom or GLTron has Joystick support? That would be a wonderful test application :) I also took the liberty of modifying some text files like BUGS, README.CVS, README.MacOSX (which now contains the OS X docs I long promised)

/*
	SDL - Simple DirectMedia Layer
	Copyright (C) 1997, 1998, 1999, 2000, 2001	Sam Lantinga

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Library General Public
	License as published by the Free Software Foundation; either
	version 2 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Library General Public License for more details.

	You should have received a copy of the GNU Library General Public
	License along with this library; if not, write to the Free
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	Sam Lantinga
	slouken@devolution.com
*/

#include "SDL_QuartzKeys.h"

static SDLKey keymap[256];
static unsigned int currentMods = 0; /* Current keyboard modifiers, to track modifier state */
static int last_virtual_button = 0; /* Last virtual mouse button pressed */

static void	 QZ_InitOSKeymap (_THIS) {
	const void *KCHRPtr;
	UInt32 state;
	UInt32 value;
	int i;
	int world = SDLK_WORLD_0;
	
	for ( i=0; i<SDL_TABLESIZE(keymap); ++i )
		keymap[i] = SDLK_UNKNOWN;

	/* This keymap is almost exactly the same as the OS 9 one */
	keymap[QZ_ESCAPE] = SDLK_ESCAPE;
	keymap[QZ_F1] = SDLK_F1;
	keymap[QZ_F2] = SDLK_F2;
	keymap[QZ_F3] = SDLK_F3;
	keymap[QZ_F4] = SDLK_F4;
	keymap[QZ_F5] = SDLK_F5;
	keymap[QZ_F6] = SDLK_F6;
	keymap[QZ_F7] = SDLK_F7;
	keymap[QZ_F8] = SDLK_F8;
	keymap[QZ_F9] = SDLK_F9;
	keymap[QZ_F10] = SDLK_F10;
	keymap[QZ_F11] = SDLK_F11;
	keymap[QZ_F12] = SDLK_F12;
	keymap[QZ_PRINT] = SDLK_PRINT;
	keymap[QZ_SCROLLOCK] = SDLK_SCROLLOCK;
	keymap[QZ_PAUSE] = SDLK_PAUSE;
	keymap[QZ_POWER] = SDLK_POWER;
	keymap[QZ_BACKQUOTE] = SDLK_BACKQUOTE;
	keymap[QZ_1] = SDLK_1;
	keymap[QZ_2] = SDLK_2;
	keymap[QZ_3] = SDLK_3;
	keymap[QZ_4] = SDLK_4;
	keymap[QZ_5] = SDLK_5;
	keymap[QZ_6] = SDLK_6;
	keymap[QZ_7] = SDLK_7;
	keymap[QZ_8] = SDLK_8;
	keymap[QZ_9] = SDLK_9;
	keymap[QZ_0] = SDLK_0;
	keymap[QZ_MINUS] = SDLK_MINUS;
	keymap[QZ_EQUALS] = SDLK_EQUALS;
	keymap[QZ_BACKSPACE] = SDLK_BACKSPACE;
	keymap[QZ_INSERT] = SDLK_INSERT;
	keymap[QZ_HOME] = SDLK_HOME;
	keymap[QZ_PAGEUP] = SDLK_PAGEUP;
	keymap[QZ_NUMLOCK] = SDLK_NUMLOCK;
	keymap[QZ_KP_EQUALS] = SDLK_KP_EQUALS;
	keymap[QZ_KP_DIVIDE] = SDLK_KP_DIVIDE;
	keymap[QZ_KP_MULTIPLY] = SDLK_KP_MULTIPLY;
	keymap[QZ_TAB] = SDLK_TAB;
	keymap[QZ_q] = SDLK_q;
	keymap[QZ_w] = SDLK_w;
	keymap[QZ_e] = SDLK_e;
	keymap[QZ_r] = SDLK_r;
	keymap[QZ_t] = SDLK_t;
	keymap[QZ_y] = SDLK_y;
	keymap[QZ_u] = SDLK_u;
	keymap[QZ_i] = SDLK_i;
	keymap[QZ_o] = SDLK_o;
	keymap[QZ_p] = SDLK_p;
	keymap[QZ_LEFTBRACKET] = SDLK_LEFTBRACKET;
	keymap[QZ_RIGHTBRACKET] = SDLK_RIGHTBRACKET;
	keymap[QZ_BACKSLASH] = SDLK_BACKSLASH;
	keymap[QZ_DELETE] = SDLK_DELETE;
	keymap[QZ_END] = SDLK_END;
	keymap[QZ_PAGEDOWN] = SDLK_PAGEDOWN;
	keymap[QZ_KP7] = SDLK_KP7;
	keymap[QZ_KP8] = SDLK_KP8;
	keymap[QZ_KP9] = SDLK_KP9;
	keymap[QZ_KP_MINUS] = SDLK_KP_MINUS;
	keymap[QZ_CAPSLOCK] = SDLK_CAPSLOCK;
	keymap[QZ_a] = SDLK_a;
	keymap[QZ_s] = SDLK_s;
	keymap[QZ_d] = SDLK_d;
	keymap[QZ_f] = SDLK_f;
	keymap[QZ_g] = SDLK_g;
	keymap[QZ_h] = SDLK_h;
	keymap[QZ_j] = SDLK_j;
	keymap[QZ_k] = SDLK_k;
	keymap[QZ_l] = SDLK_l;
	keymap[QZ_SEMICOLON] = SDLK_SEMICOLON;
	keymap[QZ_QUOTE] = SDLK_QUOTE;
	keymap[QZ_RETURN] = SDLK_RETURN;
	keymap[QZ_KP4] = SDLK_KP4;
	keymap[QZ_KP5] = SDLK_KP5;
	keymap[QZ_KP6] = SDLK_KP6;
	keymap[QZ_KP_PLUS] = SDLK_KP_PLUS;
	keymap[QZ_LSHIFT] = SDLK_LSHIFT;
	keymap[QZ_z] = SDLK_z;
	keymap[QZ_x] = SDLK_x;
	keymap[QZ_c] = SDLK_c;
	keymap[QZ_v] = SDLK_v;
	keymap[QZ_b] = SDLK_b;
	keymap[QZ_n] = SDLK_n;
	keymap[QZ_m] = SDLK_m;
	keymap[QZ_COMMA] = SDLK_COMMA;
	keymap[QZ_PERIOD] = SDLK_PERIOD;
	keymap[QZ_SLASH] = SDLK_SLASH;
	keymap[QZ_UP] = SDLK_UP;
	keymap[QZ_KP1] = SDLK_KP1;
	keymap[QZ_KP2] = SDLK_KP2;
	keymap[QZ_KP3] = SDLK_KP3;
	keymap[QZ_KP_ENTER] = SDLK_KP_ENTER;
	keymap[QZ_LCTRL] = SDLK_LCTRL;
	keymap[QZ_LALT] = SDLK_LALT;
	keymap[QZ_LMETA] = SDLK_LMETA;
	keymap[QZ_SPACE] = SDLK_SPACE;
	keymap[QZ_LEFT] = SDLK_LEFT;
	keymap[QZ_DOWN] = SDLK_DOWN;
	keymap[QZ_RIGHT] = SDLK_RIGHT;
	keymap[QZ_KP0] = SDLK_KP0;
	keymap[QZ_KP_PERIOD] = SDLK_KP_PERIOD;
	keymap[QZ_IBOOK_ENTER] = SDLK_KP_ENTER;
	keymap[QZ_IBOOK_RIGHT] = SDLK_RIGHT;
	keymap[QZ_IBOOK_DOWN] = SDLK_DOWN;
	keymap[QZ_IBOOK_UP]	  = SDLK_UP;
	keymap[QZ_IBOOK_LEFT] = SDLK_LEFT;
	
	/* Up there we setup a static scancode->keysym map. However, it will not
	 * work very well on international keyboard. Hence we now query MacOS
	 * for its own keymap to adjust our own mapping table. However, this is
	 * bascially only useful for ascii char keys. This is also the reason
	 * why we keep the static table, too.
	 */
	
	/* Get a pointer to the systems cached KCHR */
	KCHRPtr = (void *)GetScriptManagerVariable(smKCHRCache);
	if (KCHRPtr)
	{
		/* Loop over all 127 possible scan codes */
		for (i = 0; i < 0x7F; i++)
		{
			/* We pretend a clean start to begin with (i.e. no dead keys active */
			state = 0;
			
			/* Now translate the key code to a key value */
			value = KeyTranslate(KCHRPtr, i, &state) & 0xff;
			
			/* If the state become 0, it was a dead key. We need to translate again,
			passing in the new state, to get the actual key value */
			if (state != 0)
				value = KeyTranslate(KCHRPtr, i, &state) & 0xff;
			
			/* Now we should have an ascii value, or 0. Try to figure out to which SDL symbol it maps */
			if (value >= 128)	 /* Some non-ASCII char, map it to SDLK_WORLD_* */
				keymap[i] = world++;
			else if (value >= 32)	 /* non-control ASCII char */
				keymap[i] = value;
		}
	}
	
	/* The keypad codes are re-setup here, because the loop above cannot
	 * distinguish between a key on the keypad and a regular key. We maybe
	 * could get around this problem in another fashion: NSEvent's flags
	 * include a "NSNumericPadKeyMask" bit; we could check that and modify
	 * the symbol we return on the fly. However, this flag seems to exhibit
	 * some weird behaviour related to the num lock key
	 */
	keymap[QZ_KP0] = SDLK_KP0;
	keymap[QZ_KP1] = SDLK_KP1;
	keymap[QZ_KP2] = SDLK_KP2;
	keymap[QZ_KP3] = SDLK_KP3;
	keymap[QZ_KP4] = SDLK_KP4;
	keymap[QZ_KP5] = SDLK_KP5;
	keymap[QZ_KP6] = SDLK_KP6;
	keymap[QZ_KP7] = SDLK_KP7;
	keymap[QZ_KP8] = SDLK_KP8;
	keymap[QZ_KP9] = SDLK_KP9;
	keymap[QZ_KP_MINUS] = SDLK_KP_MINUS;
	keymap[QZ_KP_PLUS] = SDLK_KP_PLUS;
	keymap[QZ_KP_PERIOD] = SDLK_KP_PERIOD;
	keymap[QZ_KP_EQUALS] = SDLK_KP_EQUALS;
	keymap[QZ_KP_DIVIDE] = SDLK_KP_DIVIDE;
	keymap[QZ_KP_MULTIPLY] = SDLK_KP_MULTIPLY;
	keymap[QZ_KP_ENTER] = SDLK_KP_ENTER;
}

static void QZ_DoKey (int state, NSEvent *event) {

	NSString *chars;
	int i;
	SDL_keysym key;
	
	/* An event can contain multiple characters */
	/* I'll ignore this fact for now, since there is only one virtual key code per event */
	chars = [ event characters ];
	for (i =0; i < 1 /*[ chars length ] */; i++) {
				
		key.scancode = [ event keyCode ];
		key.sym		 = keymap [ key.scancode ];
		key.unicode	 = [ chars characterAtIndex:i];
		key.mod		 = KMOD_NONE;
					
		SDL_PrivateKeyboard (state, &key);
	}
}

static void QZ_DoModifiers (unsigned int newMods) {

	const int mapping[] = { SDLK_CAPSLOCK, SDLK_LSHIFT, SDLK_LCTRL, SDLK_LALT, SDLK_LMETA } ;

	int i;
	int bit;
	SDL_keysym key;

	key.scancode = 0;
	key.sym		 = SDLK_UNKNOWN;
	key.unicode	 = 0;
	key.mod		 = KMOD_NONE;
	
	/* Iterate through the bits, testing each against the current modifiers */
	for (i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) {
	
		unsigned int currentMask, newMask;
		
		currentMask = currentMods & bit;
		newMask		= newMods & bit;
		
		if ( currentMask &&
			 currentMask != newMask ) {	 /* modifier up event */

			key.sym = mapping[i];
			/* If this was Caps Lock, we need some additional voodoo to make SDL happy */
			if (bit == NSAlphaShiftKeyMask)
				SDL_PrivateKeyboard (SDL_PRESSED, &key);
			SDL_PrivateKeyboard (SDL_RELEASED, &key);
		}
		else
		if ( newMask &&
			 currentMask != newMask ) {	 /* modifier down event */
		
			key.sym = mapping[i];
			SDL_PrivateKeyboard (SDL_PRESSED, &key);
			/* If this was Caps Lock, we need some additional voodoo to make SDL happy */
			if (bit == NSAlphaShiftKeyMask)
				SDL_PrivateKeyboard (SDL_RELEASED, &key);
		}
	}
	
	currentMods = newMods;
}

static void QZ_DoActivate (_THIS)
{
	inForeground = YES;

	/* Regrab the mouse */
	if (currentGrabMode == SDL_GRAB_ON) {
		QZ_WarpWMCursor (this, SDL_VideoSurface->w / 2, SDL_VideoSurface->h / 2);
		CGAssociateMouseAndMouseCursorPosition (0);
	}

	/* Hide the mouse cursor if inside the app window */
	if (!QZ_cursor_visible) {
		HideCursor ();
	}
	
	SDL_PrivateAppActive (1, SDL_APPINPUTFOCUS);
}

static void QZ_DoDeactivate (_THIS) {
	
	inForeground = NO;

	/* Ungrab mouse if it is grabbed */
	if (currentGrabMode == SDL_GRAB_ON) {
		CGAssociateMouseAndMouseCursorPosition (1);
	}

	/* Show the mouse cursor */
	if (!QZ_cursor_visible) {
		ShowCursor ();
	}
	
	SDL_PrivateAppActive (0, SDL_APPINPUTFOCUS);
}

static void QZ_PumpEvents (_THIS)
{
	NSDate *distantPast;
	NSEvent *event;
	NSRect winRect;
	NSRect titleBarRect;
	NSAutoreleasePool *pool;
	
	pool = [ [ NSAutoreleasePool alloc ] init ];
	distantPast = [ NSDate distantPast ];
	
	winRect = NSMakeRect (0, 0, SDL_VideoSurface->w + 1, SDL_VideoSurface->h + 1);
	titleBarRect = NSMakeRect ( 0, SDL_VideoSurface->h, SDL_VideoSurface->w,
		SDL_VideoSurface->h + 22 );
			
	do {
	
		/* Poll for an event. This will not block */
		event = [ NSApp nextEventMatchingMask:NSAnyEventMask
					untilDate:distantPast
					inMode: NSDefaultRunLoopMode dequeue:YES ];
	
		if (event != nil) {
			unsigned int type;
			BOOL isForGameWin;

			#define DO_MOUSE_DOWN(button, sendToWindow) do {				 \
				if ( inForeground ) {										 \
					if ( (SDL_VideoSurface->flags & SDL_FULLSCREEN) ||		 \
						 NSPointInRect([event locationInWindow], winRect) )	 \
						SDL_PrivateMouseButton (SDL_PRESSED, button, 0, 0);	 \
				}															 \
				else {														 \
					QZ_DoActivate (this);									 \
				}															 \
				[ NSApp sendEvent:event ];									 \
				} while(0)
				
			#define DO_MOUSE_UP(button, sendToWindow) do {					 \
				if ( (SDL_VideoSurface->flags & SDL_FULLSCREEN) ||			 \
					 !NSPointInRect([event locationInWindow], titleBarRect) )\
					SDL_PrivateMouseButton (SDL_RELEASED, button, 0, 0);	 \
				[ NSApp sendEvent:event ];									 \
				} while(0)

			type = [ event type ];
			isForGameWin = (qz_window == [ event window ]);
			switch (type) {
			
			case NSLeftMouseDown:
				if ( NSCommandKeyMask & currentMods ) {
					last_virtual_button = 3;
					DO_MOUSE_DOWN (3, 0);
				}
				else if ( NSAlternateKeyMask & currentMods ) {
					last_virtual_button = 2;
					DO_MOUSE_DOWN (2, 0);
				}
				else {
					DO_MOUSE_DOWN (1, 1);
				}
				break;
			case 25:			   DO_MOUSE_DOWN (2, 0); break;
			case NSRightMouseDown: DO_MOUSE_DOWN (3, 0); break;	
			case NSLeftMouseUp:
			
				if ( last_virtual_button != 0 ) {
					DO_MOUSE_UP (last_virtual_button, 0);
					last_virtual_button = 0;
				}
				else {
					DO_MOUSE_UP (1, 1);
				}
				break;
			case 26:			   DO_MOUSE_UP (2, 0);	 break;
			case NSRightMouseUp:   DO_MOUSE_UP (3, 0);	 break;
			case NSSystemDefined:
				//if ([event subtype] == 7) {
				//	  unsigned int buttons;	  // up to 32 mouse button states!
				//	  buttons = [ event data2 ];
				//}
				break;
			case NSLeftMouseDragged:
			case NSRightMouseDragged:
			case 27:
			case NSMouseMoved:
				if ( (SDL_VideoSurface->flags & SDL_FULLSCREEN)
					|| NSPointInRect([event locationInWindow], winRect) )
				{
				   static int moves = 0;
				   NSPoint p;
			
				   if ( SDL_VideoSurface->flags & SDL_FULLSCREEN ) {
					   p = [ NSEvent mouseLocation ];
					   p.y = [[NSScreen mainScreen] frame].size.height - p.y;
				   } else {
					   p = [ event locationInWindow ];
					   p.y = SDL_VideoSurface->h - p.y;
				   }

				   if ( (moves % 10) == 0 ) {
						SDL_PrivateMouseMotion (0, 0, p.x, p.y);
				   }
				   else {
						CGMouseDelta dx, dy;
						CGGetLastMouseDelta (&dx, &dy);
						SDL_PrivateMouseMotion (0, 1, dx, dy);
				   }
				   moves++;
				}
				break;
			case NSScrollWheel:
				{
					if (NSPointInRect([ event locationInWindow ], winRect)) {
						float dy;
						dy = [ event deltaY ];
						if ( dy > 0.0 ) /* Scroll up */
							SDL_PrivateMouseButton (SDL_PRESSED, 4, 0, 0);
						else /* Scroll down */
							SDL_PrivateMouseButton (SDL_PRESSED, 5, 0, 0);
					}
				}
				break;
			case NSKeyUp:
				QZ_DoKey (SDL_RELEASED, event);
				break;
			case NSKeyDown:
				QZ_DoKey (SDL_PRESSED, event);
				break;
			case NSFlagsChanged:
				QZ_DoModifiers( [ event modifierFlags ] );
				break;
			/* case NSMouseEntered: break; */
			/* case NSMouseExited: break; */
			case NSAppKitDefined:
				switch ( [ event subtype ] ) {
				case NSApplicationActivatedEventType:
					QZ_DoActivate (this);
					break;
				case NSApplicationDeactivatedEventType:
					QZ_DoDeactivate (this);
					break;
				}
				[ NSApp sendEvent:event ];
				break;
			/* case NSApplicationDefined: break; */
			/* case NSPeriodic: break; */
			/* case NSCursorUpdate: break; */
			default:
				[ NSApp sendEvent:event ];
			}
		}
	  } while (event != nil);
	
	  [ pool release ];
}