Просмотр исходного кода

Gamepad support via libstem_gamepad

Ivan Safrin 14 лет назад
Родитель
Сommit
b07cef3dd0

+ 3 - 0
.gitignore

@@ -41,8 +41,11 @@ Player/Build/Mac OS X/Polycode Player.xcodeproj/project.xcworkspace/xcuserdata/i
 
 /Player/Build
 /Standalone/Build
+
 /Dependencies/Build
 /Dependencies/Downloads
+/Dependencies/Local/libstem_gamepad/Build
+
 /Core/Build/Mac\ OS\ X/build
 /IDE/Build/Mac\ OS\ X/build
 /Modules/Build/Mac\ OS\ X/build

+ 2 - 0
Core/Contents/CMakeLists.txt

@@ -17,6 +17,7 @@ SET(polycore_SRCS
     Source/PolyEvent.cpp
     Source/PolyEventDispatcher.cpp
     Source/PolyEventHandler.cpp
+    Source/PolyStemGamepad.cpp
     Source/PolyFixedShader.cpp
     Source/PolyFont.cpp
     Source/PolyFontManager.cpp
@@ -106,6 +107,7 @@ SET(polycore_HDRS
     Include/PolyEventDispatcher.h
     Include/PolyEvent.h
     Include/PolyEventHandler.h
+    Include/PolyStemGamepad.h
     Include/PolyFixedShader.h
     Include/PolyFont.h
     Include/PolyFontManager.h

+ 2 - 1
Core/Contents/Include/PolyCocoaCore.h

@@ -34,6 +34,7 @@
 #include <unistd.h>
 #include "PolyInputEvent.h"
 #include "PolyGLSLShaderModule.h"
+#include "PolyStemGamepad.h"
 
 #import <Cocoa/Cocoa.h>
 
@@ -110,7 +111,7 @@ namespace Polycode {
 		
 	protected:	
 		PolycodeView *glView;
-		
+		StemGamepadController *gamepadController;
 		uint64_t initTime;		
 				
 	};

+ 20 - 1
Core/Contents/Include/PolyCoreInput.h

@@ -29,6 +29,15 @@ THE SOFTWARE.
 
 namespace Polycode {
 	
+	class JoystickInfo {
+		public:
+			JoystickInfo();
+			
+			float joystickAxisState[32];
+			bool joystickButtonState[64];
+			unsigned int deviceID;		
+	};
+	
 	class InputEvent;
 
 	/**
@@ -84,7 +93,16 @@ namespace Polycode {
 		*/								
 		bool getMouseButtonState(int mouseButton);		
 
-		
+		unsigned int getNumJoysticks();
+		JoystickInfo *getJoystickInfoByIndex(unsigned int index);
+
+		JoystickInfo *getJoystickInfoByID(unsigned int deviceID);
+		void addJoystick(unsigned int deviceID);
+		void removeJoystick(unsigned int deviceID);
+		void joystickAxisMoved(unsigned int axisID, float value, unsigned int deviceID);
+		void joystickButtonDown(unsigned int buttonID, unsigned int deviceID);
+		void joystickButtonUp(unsigned int buttonID, unsigned int deviceID);
+						
 		void mouseWheelUp(int ticks);
 		void mouseWheelDown(int ticks);
 		void setMouseButtonState(int mouseButton, bool state, int ticks);
@@ -98,6 +116,7 @@ namespace Polycode {
 		
 	protected:
 		
+		std::vector<JoystickInfo> joysticks;
 		bool keyboardState[512];
 		bool mouseButtons[3];
 		Vector2 mousePosition;

+ 14 - 0
Core/Contents/Include/PolyInputEvent.h

@@ -33,6 +33,7 @@ namespace Polycode {
 	*/
 	class _PolyExport InputEvent : public Event {
 		public:
+			InputEvent();
 			InputEvent(Vector2 mousePosition,int timestamp);
 //			InputEvent(PolyKEY key, int timestamp);
 			InputEvent(PolyKEY key, wchar_t charCode, int timestamp);			
@@ -56,6 +57,12 @@ namespace Polycode {
 		static const int EVENT_KEYDOWN = 13;
 		static const int EVENT_KEYUP = 14;
 		
+		static const int EVENT_JOYBUTTON_DOWN = 15;		
+		static const int EVENT_JOYBUTTON_UP = 16;
+		static const int EVENT_JOYAXIS_MOVED = 17;
+		static const int EVENT_JOYDEVICE_ATTACHED = 18;
+		static const int EVENT_JOYDEVICE_DETACHED = 19;
+		
 		//@}
 		// ----------------------------------------------------------------------------------------------------------------
 		
@@ -78,6 +85,8 @@ namespace Polycode {
 		*/		
 		PolyKEY key;
 		
+		
+		
 		int keyCode() { return key; }
 		
 		/**
@@ -86,6 +95,11 @@ namespace Polycode {
 		wchar_t charCode;
 		int timestamp;
 		
+		unsigned int joystickDeviceID;
+		float joystickAxisValue;
+		unsigned int joystickButton;
+		unsigned int joystickAxis;
+		
 		protected:
 		
 			

+ 20 - 0
Core/Contents/Include/PolyStemGamepad.h

@@ -0,0 +1,20 @@
+#pragma once
+
+#include "PolyCoreInput.h"
+
+extern "C" {
+#include "gamepad/Gamepad.h"
+
+#define POLL_ITERATION_INTERVAL 30
+
+class StemGamepadController {
+	public: 
+		StemGamepadController(Polycode::CoreInput *coreInput);
+		~StemGamepadController();
+		
+		void Update();
+		
+		unsigned int iterationsToNextPoll;
+		Polycode::CoreInput *coreInput;
+};
+}

+ 3 - 0
Core/Contents/Source/PolyCocoaCore.mm

@@ -31,6 +31,8 @@ long getThreadID() {
 
 CocoaCore::CocoaCore(PolycodeView *view, int xRes, int yRes, bool fullScreen, bool vSync, int aaLevel, int anisotropyLevel, int frameRate) : Core(xRes, yRes, fullScreen, vSync, aaLevel, anisotropyLevel, frameRate) {	
 	eventMutex = createMutex();
+
+	gamepadController = new StemGamepadController(input);
 	
 //	NSLog(@"BUNDLE: %@", [[NSBundle mainBundle] bundlePath]);
 	chdir([[[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/Contents/Resources"] UTF8String]);
@@ -314,6 +316,7 @@ void CocoaCore::setCursor(int cursorType) {
 
 void CocoaCore::checkEvents() {
 	lockMutex(eventMutex);
+	gamepadController->Update();
 	CocoaEvent event;
 	for(int i=0; i < cocoaEvents.size(); i++) {
 		event = cocoaEvents[i];

+ 84 - 1
Core/Contents/Source/PolyCoreInput.cpp

@@ -25,6 +25,16 @@
 
 namespace Polycode {
 	
+	JoystickInfo::JoystickInfo() {
+		for(int i=0; i < 32; i++) {
+			joystickAxisState[i] = 0.0;
+		}
+
+		for(int i=0; i < 64; i++) {
+			joystickButtonState[i] = false;
+		}	
+	}
+	
 	CoreInput::CoreInput() : EventDispatcher() {
 		mouseButtons[0] = false;
 		mouseButtons[1] = false;
@@ -32,12 +42,85 @@ namespace Polycode {
 		
 		for(int i=0; i < 512; i++) {
 			keyboardState[i] = 0;
-		}
+		}		
 	}
 	
 	CoreInput::~CoreInput() {
 	}
 	
+	unsigned int CoreInput::getNumJoysticks() {
+		return joysticks.size();
+	}
+	
+	JoystickInfo *CoreInput::getJoystickInfoByIndex(unsigned int index) {
+		return &joysticks[index];
+	}	
+	
+	JoystickInfo *CoreInput::getJoystickInfoByID(unsigned int deviceID) {
+		for(int i=0;i<joysticks.size();i++) {
+			if(joysticks[i].deviceID == deviceID) {
+				return &joysticks[i];
+			}
+		}
+		return NULL;
+	}
+			
+	void CoreInput::addJoystick(unsigned int deviceID) {
+		JoystickInfo joystick;
+		joystick.deviceID = deviceID;
+		joysticks.push_back(joystick);
+		InputEvent *evt = new InputEvent();
+		evt->joystickDeviceID = deviceID;
+		dispatchEvent(evt, InputEvent::EVENT_JOYDEVICE_ATTACHED);				
+	}
+	
+	void CoreInput::removeJoystick(unsigned int deviceID) {
+		for(int i=0;i<joysticks.size();i++) {
+			if(joysticks[i].deviceID == deviceID) {
+				joysticks.erase(joysticks.begin()+i);
+				InputEvent *evt = new InputEvent();
+				evt->joystickDeviceID = deviceID;
+				dispatchEvent(evt, InputEvent::EVENT_JOYDEVICE_DETACHED);
+				return;
+			}
+		}	
+	}
+	
+	void CoreInput::joystickAxisMoved(unsigned int axisID, float value, unsigned int deviceID) {
+		JoystickInfo *info = getJoystickInfoByID(deviceID);
+		if(info) {
+			info->joystickAxisState[axisID] = value;
+			InputEvent *evt = new InputEvent();
+			evt->joystickDeviceID = deviceID;
+			evt->joystickAxis = axisID;
+			evt->joystickAxisValue = value;
+			dispatchEvent(evt, InputEvent::EVENT_JOYAXIS_MOVED);
+		}	
+	}
+	
+	void CoreInput::joystickButtonDown(unsigned int buttonID, unsigned int deviceID) {
+		JoystickInfo *info = getJoystickInfoByID(deviceID);
+		if(info) {
+			info->joystickButtonState[buttonID] = true;
+			InputEvent *evt = new InputEvent();
+			evt->joystickDeviceID = deviceID;
+			evt->joystickButton = buttonID;
+			dispatchEvent(evt, InputEvent::EVENT_JOYBUTTON_DOWN);
+		}		
+	}
+	
+	void CoreInput::joystickButtonUp(unsigned int buttonID, unsigned int deviceID) {
+		JoystickInfo *info = getJoystickInfoByID(deviceID);
+		if(info) {
+			info->joystickButtonState[buttonID] = false;
+			InputEvent *evt = new InputEvent();
+			evt->joystickDeviceID = deviceID;
+			evt->joystickButton = buttonID;
+			dispatchEvent(evt, InputEvent::EVENT_JOYBUTTON_UP);
+		}	
+	}
+	
+	
 	bool CoreInput::getMouseButtonState(int mouseButton) {
 		return mouseButtons[mouseButton];
 	}

+ 5 - 0
Core/Contents/Source/PolyInputEvent.cpp

@@ -24,6 +24,10 @@
 
 namespace Polycode {
 
+InputEvent::InputEvent() : Event() {
+	eventType = "InputEvent";
+}
+
 InputEvent::InputEvent(Vector2 mousePosition, int timestamp) : Event() {
 	this->mousePosition = mousePosition;
 	this->timestamp = timestamp;
@@ -34,6 +38,7 @@ InputEvent::InputEvent(PolyKEY key, wchar_t charCode, int timestamp) : Event() {
 	this->key = key;
 	this->charCode = charCode;
 	this->timestamp = timestamp;
+	eventType = "InputEvent";	
 }
 
 /*

+ 71 - 0
Core/Contents/Source/PolyStemGamepad.cpp

@@ -0,0 +1,71 @@
+#include "PolyStemGamepad.h"
+
+extern "C" {
+bool onButtonDown(void * sender, const char * eventID, void * eventData, void * context) {
+	struct Gamepad_buttonEvent * event;	
+	event = (Gamepad_buttonEvent*)eventData;
+	StemGamepadController *controller = (StemGamepadController*) context;
+	controller->coreInput->joystickButtonDown(event->buttonID, event->device->deviceID);	
+	return true;
+}
+
+bool onButtonUp(void * sender, const char * eventID, void * eventData, void * context) {
+	struct Gamepad_buttonEvent * event;
+	event = (Gamepad_buttonEvent *)eventData;	
+	StemGamepadController *controller = (StemGamepadController*) context;
+	controller->coreInput->joystickButtonUp(event->buttonID, event->device->deviceID);
+	return true;
+}
+
+bool onAxisMoved(void * sender, const char * eventID, void * eventData, void * context) {
+	struct Gamepad_axisEvent * event;	
+	event = (Gamepad_axisEvent *)eventData;
+	StemGamepadController *controller = (StemGamepadController*) context;
+	controller->coreInput->joystickAxisMoved(event->axisID, event->value, event->device->deviceID);
+	return true;
+}
+
+bool onDeviceAttached(void * sender, const char * eventID, void * eventData, void * context) {
+	struct Gamepad_device * device;	
+	device = (Gamepad_device*) eventData;
+	
+	StemGamepadController *controller = (StemGamepadController*) context;
+	controller->coreInput->addJoystick(device->deviceID);
+
+	device->eventDispatcher->registerForEvent(device->eventDispatcher, GAMEPAD_EVENT_BUTTON_DOWN, onButtonDown, controller);
+	device->eventDispatcher->registerForEvent(device->eventDispatcher, GAMEPAD_EVENT_BUTTON_UP, onButtonUp, controller);
+	device->eventDispatcher->registerForEvent(device->eventDispatcher, GAMEPAD_EVENT_AXIS_MOVED, onAxisMoved, controller);
+
+	return true;
+}
+
+bool onDeviceRemoved(void * sender, const char * eventID, void * eventData, void * context) {
+	struct Gamepad_device * device;	
+	device = (Gamepad_device *)eventData;
+	StemGamepadController *controller = (StemGamepadController*) context;
+	controller->coreInput->removeJoystick(device->deviceID);	
+	return true;
+}
+
+StemGamepadController::StemGamepadController(Polycode::CoreInput *coreInput) {
+	this->coreInput = coreInput;
+	Gamepad_eventDispatcher()->registerForEvent(Gamepad_eventDispatcher(), GAMEPAD_EVENT_DEVICE_ATTACHED, onDeviceAttached, this);
+	Gamepad_eventDispatcher()->registerForEvent(Gamepad_eventDispatcher(), GAMEPAD_EVENT_DEVICE_REMOVED, onDeviceRemoved, this);
+	Gamepad_init();
+	iterationsToNextPoll = POLL_ITERATION_INTERVAL;
+}
+
+StemGamepadController::~StemGamepadController() {
+
+}
+
+void StemGamepadController::Update() {
+	iterationsToNextPoll--;
+	if (iterationsToNextPoll == 0) {
+		Gamepad_detectDevices();
+		iterationsToNextPoll = POLL_ITERATION_INTERVAL;
+	}
+	Gamepad_processEvents();
+	
+}
+}

+ 43 - 0
Dependencies/Local/libstem_gamepad/CMakeLists.txt

@@ -0,0 +1,43 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
+PROJECT(stem_gamepad)
+
+SET(POLYCODE_RELEASE_DIR ${stem_gamepad_SOURCE_DIR}/../../../Release/${CMAKE_SYSTEM_NAME})
+SET(CMAKE_INSTALL_PREFIX ${POLYCODE_RELEASE_DIR}/Framework/)
+
+SET(stem_gamepad_HDRS
+	gamepad/Gamepad.h
+	utilities/EventDispatcher.h
+)
+
+IF(APPLE)
+SET(stem_gamepad_SRCS
+	utilities/EventDispatcher.c
+	gamepad/Gamepad_macosx.c
+)
+ENDIF(APPLE)
+
+INCLUDE_DIRECTORIES(
+    ${BOX2D_INCLUDE_DIRS}
+	./
+	utilities
+	gamepad
+)
+
+SET(CMAKE_DEBUG_POSTFIX "_d")
+
+ADD_LIBRARY(stem_gamepad ${stem_gamepad_SRCS} ${stem_gamepad_HDRS})
+
+    # install headers
+    INSTALL(FILES gamepad/Gamepad.h DESTINATION Core/Dependencies/Include/gamepad)
+    INSTALL(FILES utilities/EventDispatcher.h DESTINATION Core/Dependencies/Include/utilities)
+
+    # install libraries
+#    IF(POLYCODE_BUILD_SHARED)
+    INSTALL(TARGETS stem_gamepad EXPORT stem_gamepad-targets DESTINATION Core/Dependencies/lib)
+#    ENDIF(POLYCODE_BUILD_SHARED)
+#    IF(POLYCODE_BUILD_STATIC)
+#        INSTALL(TARGETS Polycode2DPhysics_static DESTINATION ${POLYCODE_RELEASE_DIR}/Framework/Modules/lib)
+#    ENDIF(POLYCODE_BUILD_STATIC)
+
+    INSTALL(EXPORT stem_gamepad-targets DESTINATION
+        Core/Dependencies/lib/stem_gamepad)

+ 128 - 0
Dependencies/Local/libstem_gamepad/gamepad/Gamepad.h

@@ -0,0 +1,128 @@
+/*
+  Copyright (c) 2010 Alex Diener
+  
+  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.
+  
+  Alex Diener [email protected]
+*/
+
+#ifndef __GAMEPAD_H__
+#define __GAMEPAD_H__
+
+#include <stdbool.h>
+#include "utilities/EventDispatcher.h"
+
+// eventData -> struct Gamepad_device
+#define GAMEPAD_EVENT_DEVICE_ATTACHED "GAMEPAD_EVENT_DEVICE_ATTACHED" // Only dispatched when Gamepad_init or Gamepad_detectDevices is called
+#define GAMEPAD_EVENT_DEVICE_REMOVED  "GAMEPAD_EVENT_DEVICE_REMOVED" // Can be dispatched at any time
+
+// eventData -> struct Gamepad_buttonEvent
+#define GAMEPAD_EVENT_BUTTON_DOWN     "GAMEPAD_EVENT_BUTTON_DOWN" // Only dispatched when Gamepad_processEvents is called
+#define GAMEPAD_EVENT_BUTTON_UP       "GAMEPAD_EVENT_BUTTON_UP" // Only dispatched when Gamepad_processEvents is called
+
+// eventData -> struct Gamepad_axisEvent
+#define GAMEPAD_EVENT_AXIS_MOVED      "GAMEPAD_EVENT_AXIS_MOVED" // Only dispatched when Gamepad_processEvents is called
+
+struct Gamepad_buttonEvent {
+	// Device that generated the event
+	struct Gamepad_device * device;
+	
+	// Relative time of the event, in seconds
+	double timestamp;
+	
+	// Button being pushed or released
+	unsigned int buttonID;
+	
+	// True if button is down
+	bool down;
+};
+
+struct Gamepad_axisEvent {
+	// Device that generated the event
+	struct Gamepad_device * device;
+	
+	// Relative time of the event, in seconds
+	double timestamp;
+	
+	// Axis being moved
+	unsigned int axisID;
+	
+	// Axis position value, in the range [-1..1]
+	float value;
+};
+
+struct Gamepad_device {
+	// Unique device identifier for application session. If a device is removed and subsequently reattached during the same application session, it will have a new deviceID.
+	unsigned int deviceID;
+	
+	// Human-readable device name
+	const char * description;
+	
+	// USB vendor/product IDs as returned by the driver. Can be used to determine the particular model of device represented.
+	int vendorID;
+	int productID;
+	
+	// Number of axis elements belonging to the device
+	unsigned int numAxes;
+	
+	// Number of button elements belonging to the device
+	unsigned int numButtons;
+	
+	// Array[numAxes] of values representing the current state of each axis, in the range [-1..1]
+	float * axisStates;
+	
+	// Array[numButtons] of values representing the current state of each button
+	bool * buttonStates;
+	
+	// Broadcasts GAMEPAD_EVENT_BUTTON_DOWN, GAMEPAD_EVENT_BUTTON_UP, and GAMEPAD_EVENT_AXIS_MOVED
+	EventDispatcher * eventDispatcher;
+	
+	// Platform-specific device data storage; don't mess with it
+	void * privateData;
+};
+
+/* Initializes gamepad library and detects initial devices. Call this before any other Gamepad_*()
+   function, EXCEPT Gamepad_eventDispatcher(). In order to get receive GAMEPAD_EVENT_DEVICE_ATTACHED
+   events from devices detected in Gamepad_init(), you must register handlers for those events before
+   calling Gamepad_init(). */
+void Gamepad_init();
+
+/* Tears down all data structures created by the gamepad library and releases any memory that was
+   allocated. It is not necessary to call this function at application termination. */
+void Gamepad_shutdown();
+
+/* EventDispatcher used by gamepad library to broadcast GAMEPAD_EVENT_DEVICE_ATTACHED and
+   GAMEPAD_EVENT_DEVICE_REMOVED events. */
+EventDispatcher * Gamepad_eventDispatcher();
+
+/* Returns the number of currently attached gamepad devices. */
+unsigned int Gamepad_numDevices();
+
+/* Returns the specified Gamepad_device struct, or NULL if deviceIndex is out of bounds. */
+struct Gamepad_device * Gamepad_deviceAtIndex(unsigned int deviceIndex);
+
+/* Polls for any devices that have been attached since the last call to Gamepad_detectDevices() or
+   Gamepad_init(). If any new devices are found, a GAMEPAD_EVENT_DEVICE_ATTACHED event will be
+   broadcast via Gamepad_eventDispatcher() for each one. */
+void Gamepad_detectDevices();
+
+/* Reads pending input from all attached devices and broadcasts GAMEPAD_EVENT_BUTTON_DOWN,
+   GAMEPAD_EVENT_BUTTON_UP, and GAMEPAD_EVENT_AXIS_MOVED events through the eventDispatcher of the
+   device that generated the event. */
+void Gamepad_processEvents();
+
+#endif

+ 440 - 0
Dependencies/Local/libstem_gamepad/gamepad/Gamepad_linux.c

@@ -0,0 +1,440 @@
+/*
+  Copyright (c) 2010 Alex Diener
+  
+  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.
+  
+  Alex Diener [email protected]
+*/
+
+#include "gamepad/Gamepad.h"
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/input.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+struct Gamepad_devicePrivate {
+	pthread_t thread;
+	int fd;
+	char * path;
+	int buttonMap[KEY_CNT - BTN_MISC];
+	int axisMap[ABS_CNT];
+	struct input_absinfo axisInfo[ABS_CNT];
+};
+
+struct Gamepad_queuedEvent {
+	EventDispatcher * dispatcher;
+	const char * eventType;
+	void * eventData;
+};
+
+static struct Gamepad_device ** devices = NULL;
+static unsigned int numDevices = 0;
+static unsigned int nextDeviceID = 0;
+static pthread_mutex_t devicesMutex;
+
+static struct Gamepad_queuedEvent * eventQueue = NULL;
+static size_t eventQueueSize = 0;
+static size_t eventCount = 0;
+static pthread_mutex_t eventQueueMutex;
+
+static EventDispatcher * eventDispatcher = NULL;
+static bool inited = false;
+
+#define test_bit(bitIndex, array) \
+	((array[(bitIndex) / (sizeof(int) * 8)] >> ((bitIndex) % (sizeof(int) * 8))) & 0x1)
+
+static char ** findGamepadPaths(unsigned int * outNumGamepads) {
+	DIR * dev_input;
+	struct dirent * entity;
+	unsigned int numGamepads = 0;
+	char ** gamepadDevs = NULL;
+	unsigned int charsConsumed;
+	int num;
+	int fd;
+	int evCapBits[(EV_CNT - 1) / sizeof(int) * 8 + 1];
+	int evKeyBits[(KEY_CNT - 1) / sizeof(int) * 8 + 1];
+	int evAbsBits[(ABS_CNT - 1) / sizeof(int) * 8 + 1];
+	char fileName[PATH_MAX];
+	
+	dev_input = opendir("/dev/input");
+	if (dev_input != NULL) {
+		for (entity = readdir(dev_input); entity != NULL; entity = readdir(dev_input)) {
+			charsConsumed = 0;
+			if (sscanf(entity->d_name, "event%d%n", &num, &charsConsumed) && charsConsumed == strlen(entity->d_name)) {
+				snprintf(fileName, PATH_MAX, "/dev/input/%s", entity->d_name);
+				fd = open(fileName, O_RDONLY, 0);
+				memset(evCapBits, 0, sizeof(evCapBits));
+				memset(evKeyBits, 0, sizeof(evKeyBits));
+				memset(evAbsBits, 0, sizeof(evAbsBits));
+				if (ioctl(fd, EVIOCGBIT(0, sizeof(evCapBits)), evCapBits) < 0 ||
+				    ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(evKeyBits)), evKeyBits) < 0 ||
+				    ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(evAbsBits)), evAbsBits) < 0) {
+					close(fd);
+					continue;
+				}
+				if (!test_bit(EV_KEY, evCapBits) || !test_bit(EV_ABS, evCapBits) ||
+				    !test_bit(ABS_X, evAbsBits) || !test_bit(ABS_Y, evAbsBits) ||
+				    (!test_bit(BTN_TRIGGER, evKeyBits) && !test_bit(BTN_A, evKeyBits) && !test_bit(BTN_1, evKeyBits))) {
+					close(fd);
+					continue;
+				}
+				close(fd);
+				
+				numGamepads++;
+				gamepadDevs = realloc(gamepadDevs, sizeof(char *) * numGamepads);
+				gamepadDevs[numGamepads - 1] = malloc(strlen(fileName) + 1);
+				strcpy(gamepadDevs[numGamepads - 1], fileName);
+			}
+		}
+		closedir(dev_input);
+	}
+	
+	*outNumGamepads = numGamepads;
+	return gamepadDevs;
+}
+
+void Gamepad_init() {
+	if (!inited) {
+		pthread_mutex_init(&devicesMutex, NULL);
+		pthread_mutex_init(&eventQueueMutex, NULL);
+		inited = true;
+		Gamepad_detectDevices();
+	}
+}
+
+static void disposeDevice(struct Gamepad_device * device) {
+	device->eventDispatcher->dispose(device->eventDispatcher);
+	
+	close(((struct Gamepad_devicePrivate *) device->privateData)->fd);
+	free(((struct Gamepad_devicePrivate *) device->privateData)->path);
+	free(device->privateData);
+	
+	free((void *) device->description);
+	free(device->axisStates);
+	free(device->buttonStates);
+	free(device->eventDispatcher);
+	
+	free(device);
+}
+
+void Gamepad_shutdown() {
+	if (inited) {
+		unsigned int eventIndex;
+		unsigned int devicesLeft;
+		unsigned int gamepadIndex;
+		
+		do {
+			pthread_mutex_lock(&devicesMutex);
+			devicesLeft = numDevices;
+			if (devicesLeft > 0) {
+				pthread_t thread;
+				
+				thread = ((struct Gamepad_devicePrivate *) devices[0]->privateData)->thread;
+				pthread_cancel(thread);
+				pthread_join(thread, NULL);
+				
+				numDevices--;
+				for (gamepadIndex = 0; gamepadIndex < numDevices; gamepadIndex++) {
+					devices[gamepadIndex] = devices[gamepadIndex + 1];
+				}
+			}
+			pthread_mutex_unlock(&devicesMutex);
+		} while (devicesLeft > 0);
+		
+		pthread_mutex_destroy(&devicesMutex);
+		pthread_mutex_destroy(&eventQueueMutex);
+		free(devices);
+		devices = NULL;
+		
+		for (eventIndex = 0; eventIndex < eventCount; eventIndex++) {
+			if (!strcmp(eventQueue[eventIndex].eventType, GAMEPAD_EVENT_DEVICE_REMOVED)) {
+				disposeDevice(eventQueue[eventIndex].eventData);
+			}
+		}
+		
+		eventQueueSize = 0;
+		eventCount = 0;
+		free(eventQueue);
+		eventQueue = NULL;
+		
+		if (eventDispatcher != NULL) {
+			eventDispatcher->dispose(eventDispatcher);
+			free(eventDispatcher);
+			eventDispatcher = NULL;
+		}
+		
+		inited = false;
+	}
+}
+
+EventDispatcher * Gamepad_eventDispatcher() {
+	if (eventDispatcher == NULL) {
+		eventDispatcher = EventDispatcher_create(NULL);
+	}
+	return eventDispatcher;
+}
+
+unsigned int Gamepad_numDevices() {
+	unsigned int result;
+	
+	pthread_mutex_lock(&devicesMutex);
+	result = numDevices;
+	pthread_mutex_unlock(&devicesMutex);
+	return result;
+}
+
+struct Gamepad_device * Gamepad_deviceAtIndex(unsigned int deviceIndex) {
+	struct Gamepad_device * result;	
+	
+	pthread_mutex_lock(&devicesMutex);
+	if (deviceIndex >= numDevices) {
+		result = NULL;
+	} else {
+		result = devices[deviceIndex];
+	}
+	pthread_mutex_unlock(&devicesMutex);
+	
+	return result;
+}
+
+static void queueEvent(EventDispatcher * dispatcher, const char * eventType, void * eventData) {
+	struct Gamepad_queuedEvent queuedEvent;
+	
+	queuedEvent.dispatcher = dispatcher;
+	queuedEvent.eventType = eventType;
+	queuedEvent.eventData = eventData;
+	
+	pthread_mutex_lock(&eventQueueMutex);
+	if (eventCount >= eventQueueSize) {
+		eventQueueSize = eventQueueSize == 0 ? 1 : eventQueueSize * 2;
+		eventQueue = realloc(eventQueue, sizeof(struct Gamepad_queuedEvent) * eventQueueSize);
+	}
+	eventQueue[eventCount++] = queuedEvent;
+	pthread_mutex_unlock(&eventQueueMutex);
+}
+
+static void queueAxisEvent(struct Gamepad_device * device, double timestamp, unsigned int axisID, float value) {
+	struct Gamepad_axisEvent * axisEvent;
+	
+	axisEvent = malloc(sizeof(struct Gamepad_axisEvent));
+	axisEvent->device = device;
+	axisEvent->timestamp = timestamp;
+	axisEvent->axisID = axisID;
+	axisEvent->value = value;
+	
+	queueEvent(device->eventDispatcher, GAMEPAD_EVENT_AXIS_MOVED, axisEvent);
+}
+
+static void queueButtonEvent(struct Gamepad_device * device, double timestamp, unsigned int buttonID, bool down) {
+	struct Gamepad_buttonEvent * buttonEvent;
+	
+	buttonEvent = malloc(sizeof(struct Gamepad_buttonEvent));
+	buttonEvent->device = device;
+	buttonEvent->timestamp = timestamp;
+	buttonEvent->buttonID = buttonID;
+	buttonEvent->down = down;
+	
+	queueEvent(device->eventDispatcher, down ? GAMEPAD_EVENT_BUTTON_DOWN : GAMEPAD_EVENT_BUTTON_UP, buttonEvent);
+}
+
+static void * deviceThread(void * context) {
+	unsigned int gamepadIndex;
+	struct Gamepad_device * device;
+	struct Gamepad_devicePrivate * devicePrivate;
+	struct input_event event;
+	
+	device = context;
+	devicePrivate = device->privateData;
+	
+	while (read(devicePrivate->fd, &event, sizeof(struct input_event)) > 0) {
+		if (event.type == EV_ABS) {
+			float value;
+			
+			if (event.code > ABS_MAX || devicePrivate->axisMap[event.code] == -1) {
+				continue;
+			}
+			
+			value = (event.value - devicePrivate->axisInfo[event.code].minimum) / (float) (devicePrivate->axisInfo[event.code].maximum - devicePrivate->axisInfo[event.code].minimum) * 2.0f - 1.0f;
+			queueAxisEvent(device,
+			               event.time.tv_sec + event.time.tv_usec * 0.000001,
+			               devicePrivate->axisMap[event.code],
+			               value);
+			
+			device->axisStates[devicePrivate->axisMap[event.code]] = value;
+			
+		} else if (event.type == EV_KEY) {
+			if (event.code < BTN_MISC || event.code > KEY_MAX || devicePrivate->buttonMap[event.code - BTN_MISC] == -1) {
+				continue;
+			}
+			
+			queueButtonEvent(device,
+			                 event.time.tv_sec + event.time.tv_usec * 0.000001,
+			                 devicePrivate->buttonMap[event.code - BTN_MISC],
+			                 !!event.value);
+			
+			device->buttonStates[devicePrivate->buttonMap[event.code - BTN_MISC]] = !!event.value;
+		}
+	}
+	
+	queueEvent(eventDispatcher, GAMEPAD_EVENT_DEVICE_REMOVED, device);
+	
+	pthread_mutex_lock(&devicesMutex);
+	for (gamepadIndex = 0; gamepadIndex < numDevices; gamepadIndex++) {
+		if (devices[gamepadIndex] == device) {
+			unsigned int gamepadIndex2;
+			
+			numDevices--;
+			for (gamepadIndex2 = gamepadIndex; gamepadIndex2 < numDevices; gamepadIndex2++) {
+				devices[gamepadIndex2] = devices[gamepadIndex2 + 1];
+			}
+			gamepadIndex--;
+		}
+	}
+	pthread_mutex_unlock(&devicesMutex);
+	
+	return NULL;
+}
+
+void Gamepad_detectDevices() {
+	unsigned int numPaths;
+	char ** gamepadPaths;
+	bool duplicate;
+	unsigned int pathIndex, gamepadIndex;
+	struct stat statBuf;
+	struct Gamepad_device * deviceRecord;
+	struct Gamepad_devicePrivate * deviceRecordPrivate;
+	int fd;
+	char name[128];
+	char * description;
+	int evKeyBits[(KEY_CNT - 1) / sizeof(int) * 8 + 1];
+	int evAbsBits[(ABS_CNT - 1) / sizeof(int) * 8 + 1];
+	int bit;
+	struct input_id id;
+	
+	if (!inited) {
+		return;
+	}
+	
+	gamepadPaths = findGamepadPaths(&numPaths);
+	
+	pthread_mutex_lock(&devicesMutex);
+	for (pathIndex = 0; pathIndex < numPaths; pathIndex++) {
+		duplicate = false;
+		for (gamepadIndex = 0; gamepadIndex < numDevices; gamepadIndex++) {
+			if (!strcmp(((struct Gamepad_devicePrivate *) devices[gamepadIndex]->privateData)->path, gamepadPaths[pathIndex])) {
+				duplicate = true;
+				break;
+			}
+		}
+		if (duplicate) {
+			free(gamepadPaths[pathIndex]);
+			continue;
+		}
+		
+		if (!stat(gamepadPaths[pathIndex], &statBuf)) {
+			deviceRecord = malloc(sizeof(struct Gamepad_device));
+			deviceRecord->deviceID = nextDeviceID++;
+			deviceRecord->eventDispatcher = EventDispatcher_create(deviceRecord);
+			devices = realloc(devices, sizeof(struct Gamepad_device *) * (numDevices + 1));
+			devices[numDevices++] = deviceRecord;
+			
+			fd = open(gamepadPaths[pathIndex], O_RDONLY, 0);
+			
+			deviceRecordPrivate = malloc(sizeof(struct Gamepad_devicePrivate));
+			deviceRecordPrivate->fd = fd;
+			deviceRecordPrivate->path = gamepadPaths[pathIndex];
+			memset(deviceRecordPrivate->buttonMap, 0xFF, sizeof(deviceRecordPrivate->buttonMap));
+			memset(deviceRecordPrivate->axisMap, 0xFF, sizeof(deviceRecordPrivate->axisMap));
+			deviceRecord->privateData = deviceRecordPrivate;
+			
+			if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) > 0) {
+				description = malloc(strlen(name + 1));
+				strcpy(description, name);
+			} else {
+				description = malloc(strlen(gamepadPaths[pathIndex] + 1));
+				strcpy(description, gamepadPaths[pathIndex]);
+			}
+			deviceRecord->description = description;
+			
+			if (!ioctl(fd, EVIOCGID, &id)) {
+				deviceRecord->vendorID = id.vendor;
+				deviceRecord->productID = id.product;
+			} else {
+				deviceRecord->vendorID = deviceRecord->productID = 0;
+			}
+			
+			memset(evKeyBits, 0, sizeof(evKeyBits));
+			memset(evAbsBits, 0, sizeof(evAbsBits));
+			ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(evKeyBits)), evKeyBits);
+			ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(evAbsBits)), evAbsBits);
+			
+			deviceRecord->numAxes = 0;
+			for (bit = 0; bit < ABS_CNT; bit++) {
+				if (test_bit(bit, evAbsBits)) {
+					if (ioctl(fd, EVIOCGABS(bit), &deviceRecordPrivate->axisInfo[bit]) < 0 ||
+					    deviceRecordPrivate->axisInfo[bit].minimum == deviceRecordPrivate->axisInfo[bit].maximum) {
+						continue;
+					}
+					deviceRecordPrivate->axisMap[bit] = deviceRecord->numAxes;
+					deviceRecord->numAxes++;
+				}
+			}
+			deviceRecord->numButtons = 0;
+			for (bit = BTN_MISC; bit < KEY_CNT; bit++) {
+				if (test_bit(bit, evKeyBits)) {
+					deviceRecordPrivate->buttonMap[bit - BTN_MISC] = deviceRecord->numButtons;
+					deviceRecord->numButtons++;
+				}
+			}
+			
+			deviceRecord->axisStates = calloc(sizeof(float), deviceRecord->numAxes);
+			deviceRecord->buttonStates = calloc(sizeof(bool), deviceRecord->numButtons);
+			
+			Gamepad_eventDispatcher()->dispatchEvent(Gamepad_eventDispatcher(), GAMEPAD_EVENT_DEVICE_ATTACHED, deviceRecord);
+			
+			pthread_create(&deviceRecordPrivate->thread, NULL, deviceThread, deviceRecord);
+		}
+	}
+	pthread_mutex_unlock(&devicesMutex);
+}
+
+void Gamepad_processEvents() {
+	unsigned int eventIndex;	
+	
+	if (!inited) {
+		return;
+	}
+	
+	pthread_mutex_lock(&eventQueueMutex);
+	for (eventIndex = 0; eventIndex < eventCount; eventIndex++) {
+		eventQueue[eventIndex].dispatcher->dispatchEvent(eventQueue[eventIndex].dispatcher, eventQueue[eventIndex].eventType, eventQueue[eventIndex].eventData);
+		if (!strcmp(eventQueue[eventIndex].eventType, GAMEPAD_EVENT_DEVICE_REMOVED)) {
+			disposeDevice(eventQueue[eventIndex].eventData);
+		}
+	}
+	eventCount = 0;
+	pthread_mutex_unlock(&eventQueueMutex);
+}
+

+ 525 - 0
Dependencies/Local/libstem_gamepad/gamepad/Gamepad_macosx.c

@@ -0,0 +1,525 @@
+/*
+  Copyright (c) 2010 Alex Diener
+  
+  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.
+  
+  Alex Diener [email protected]
+*/
+
+#include "gamepad/Gamepad.h"
+#include <IOKit/hid/IOHIDLib.h>
+#include <limits.h>
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+
+struct HIDGamepadAxis {
+	IOHIDElementCookie cookie;
+	CFIndex logicalMin;
+	CFIndex logicalMax;
+	bool hasNullState;
+	bool isHatSwitch;
+	bool isHatSwitchSecondAxis;
+};
+
+struct HIDGamepadButton {
+	IOHIDElementCookie cookie;
+};
+
+struct Gamepad_devicePrivate {
+	IOHIDDeviceRef deviceRef;
+	struct HIDGamepadAxis * axisElements;
+	struct HIDGamepadButton * buttonElements;
+};
+
+struct Gamepad_queuedEvent {
+	EventDispatcher * dispatcher;
+	const char * eventType;
+	void * eventData;
+};
+
+static IOHIDManagerRef hidManager = NULL;
+static struct Gamepad_device ** devices = NULL;
+static unsigned int numDevices = 0;
+static unsigned int nextDeviceID = 0;
+
+static struct Gamepad_queuedEvent * inputEventQueue = NULL;
+static size_t inputEventQueueSize = 0;
+static size_t inputEventCount = 0;
+
+static struct Gamepad_queuedEvent * deviceEventQueue = NULL;
+static size_t deviceEventQueueSize = 0;
+static size_t deviceEventCount = 0;
+
+static EventDispatcher * eventDispatcher = NULL;
+
+static void hatValueToXY(CFIndex value, CFIndex range, int * outX, int * outY) {
+	if (value == range) {
+		*outX = *outY = 0;
+		
+	} else {
+		if (value > 0 && value < range / 2) {
+			*outX = 1;
+			
+		} else if (value > range / 2) {
+			*outX = -1;
+			
+		} else {
+			*outX = 0;
+		}
+		
+		if (value > range / 4 * 3 || value < range / 4) {
+			*outY = -1;
+			
+		} else if (value > range / 4 && value < range / 4 * 3) {
+			*outY = 1;
+			
+		} else {
+			*outY = 0;
+		}
+	}
+}
+
+static void queueInputEvent(EventDispatcher * dispatcher, const char * eventType, void * eventData) {
+	struct Gamepad_queuedEvent queuedEvent;
+	
+	queuedEvent.dispatcher = dispatcher;
+	queuedEvent.eventType = eventType;
+	queuedEvent.eventData = eventData;
+	
+	if (inputEventCount >= inputEventQueueSize) {
+		inputEventQueueSize = inputEventQueueSize == 0 ? 1 : inputEventQueueSize * 2;
+		inputEventQueue = realloc(inputEventQueue, sizeof(struct Gamepad_queuedEvent) * inputEventQueueSize);
+	}
+	inputEventQueue[inputEventCount++] = queuedEvent;
+}
+
+static void queueAxisEvent(struct Gamepad_device * device, double timestamp, unsigned int axisID, float value) {
+	struct Gamepad_axisEvent * axisEvent;
+	
+	axisEvent = malloc(sizeof(struct Gamepad_axisEvent));
+	axisEvent->device = device;
+	axisEvent->timestamp = timestamp;
+	axisEvent->axisID = axisID;
+	axisEvent->value = value;
+	
+	queueInputEvent(device->eventDispatcher, GAMEPAD_EVENT_AXIS_MOVED, axisEvent);
+}
+
+static void queueButtonEvent(struct Gamepad_device * device, double timestamp, unsigned int buttonID, bool down) {
+	struct Gamepad_buttonEvent * buttonEvent;
+	
+	buttonEvent = malloc(sizeof(struct Gamepad_buttonEvent));
+	buttonEvent->device = device;
+	buttonEvent->timestamp = timestamp;
+	buttonEvent->buttonID = buttonID;
+	buttonEvent->down = down;
+	
+	queueInputEvent(device->eventDispatcher, down ? GAMEPAD_EVENT_BUTTON_DOWN : GAMEPAD_EVENT_BUTTON_UP, buttonEvent);
+}
+
+static void onDeviceValueChanged(void * context, IOReturn result, void * sender, IOHIDValueRef value) {
+	struct Gamepad_device * deviceRecord;
+	struct Gamepad_devicePrivate * hidDeviceRecord;
+	IOHIDElementRef element;
+	IOHIDElementCookie cookie;
+	unsigned int axisIndex, buttonIndex;
+	static mach_timebase_info_data_t timebaseInfo;
+	
+	if (timebaseInfo.denom == 0) {
+		mach_timebase_info(&timebaseInfo);
+	}
+	
+	deviceRecord = context;
+	hidDeviceRecord = deviceRecord->privateData;
+	element = IOHIDValueGetElement(value);
+	cookie = IOHIDElementGetCookie(element);
+	
+	for (axisIndex = 0; axisIndex < deviceRecord->numAxes; axisIndex++) {
+		if (!hidDeviceRecord->axisElements[axisIndex].isHatSwitchSecondAxis &&
+		    hidDeviceRecord->axisElements[axisIndex].cookie == cookie) {
+			CFIndex integerValue;
+			
+			if (IOHIDValueGetLength(value) > 4) {
+				// Workaround for a strange crash that occurs with PS3 controller; was getting lengths of 39 (!)
+				continue;
+			}
+			integerValue = IOHIDValueGetIntegerValue(value);
+			
+			if (hidDeviceRecord->axisElements[axisIndex].isHatSwitch) {
+				int x, y;
+				
+				// Fix for Saitek X52
+				hidDeviceRecord->axisElements[axisIndex].hasNullState = false;
+				if (!hidDeviceRecord->axisElements[axisIndex].hasNullState) {
+					if (integerValue < hidDeviceRecord->axisElements[axisIndex].logicalMin) {
+						integerValue = hidDeviceRecord->axisElements[axisIndex].logicalMax - hidDeviceRecord->axisElements[axisIndex].logicalMin + 1;
+					} else {
+						integerValue--;
+					}
+				}
+				
+				hatValueToXY(integerValue, hidDeviceRecord->axisElements[axisIndex].logicalMax - hidDeviceRecord->axisElements[axisIndex].logicalMin + 1, &x, &y);
+				
+				if (x != deviceRecord->axisStates[axisIndex]) {
+					queueAxisEvent(deviceRecord,
+					               IOHIDValueGetTimeStamp(value) * timebaseInfo.numer / timebaseInfo.denom * 0.000000001,
+					               axisIndex,
+					               x);
+					
+					deviceRecord->axisStates[axisIndex] = x;
+				}
+				
+				if (y != deviceRecord->axisStates[axisIndex + 1]) {
+					queueAxisEvent(deviceRecord,
+					               IOHIDValueGetTimeStamp(value) * timebaseInfo.numer / timebaseInfo.denom * 0.000000001,
+					               axisIndex + 1,
+					               y);
+					
+					deviceRecord->axisStates[axisIndex + 1] = y;
+				}
+				
+			} else {
+				float floatValue;
+				
+				if (integerValue < hidDeviceRecord->axisElements[axisIndex].logicalMin) {
+					hidDeviceRecord->axisElements[axisIndex].logicalMin = integerValue;
+				}
+				if (integerValue > hidDeviceRecord->axisElements[axisIndex].logicalMax) {
+					hidDeviceRecord->axisElements[axisIndex].logicalMax = integerValue;
+				}
+				floatValue = (integerValue - hidDeviceRecord->axisElements[axisIndex].logicalMin) / (float) (hidDeviceRecord->axisElements[axisIndex].logicalMax - hidDeviceRecord->axisElements[axisIndex].logicalMin) * 2.0f - 1.0f;
+				
+				queueAxisEvent(deviceRecord,
+				               IOHIDValueGetTimeStamp(value) * timebaseInfo.numer / timebaseInfo.denom * 0.000000001,
+				               axisIndex,
+				               floatValue);
+				
+				deviceRecord->axisStates[axisIndex] = floatValue;
+			}
+			
+			return;
+		}
+	}
+	
+	for (buttonIndex = 0; buttonIndex < deviceRecord->numButtons; buttonIndex++) {
+		if (hidDeviceRecord->buttonElements[buttonIndex].cookie == cookie) {
+			bool down;
+			
+			down = IOHIDValueGetIntegerValue(value);
+			queueButtonEvent(deviceRecord,
+			                 IOHIDValueGetTimeStamp(value) * timebaseInfo.numer / timebaseInfo.denom * 0.000000001,
+			                 buttonIndex,
+			                 down);
+			
+			deviceRecord->buttonStates[buttonIndex] = down;
+			
+			return;
+		}
+	}
+}
+
+static int IOHIDDeviceGetIntProperty(IOHIDDeviceRef deviceRef, CFStringRef key) {
+	CFTypeRef typeRef;
+	int value;
+	
+	typeRef = IOHIDDeviceGetProperty(deviceRef, key);
+	if (typeRef == NULL || CFGetTypeID(typeRef) != CFNumberGetTypeID()) {
+		return 0;
+	}
+	
+	CFNumberGetValue((CFNumberRef) typeRef, kCFNumberSInt32Type, &value);
+	return value;
+}
+
+static int IOHIDDeviceGetVendorID(IOHIDDeviceRef deviceRef) {
+	return IOHIDDeviceGetIntProperty(deviceRef, CFSTR(kIOHIDVendorIDKey));
+}
+
+static int IOHIDDeviceGetProductID(IOHIDDeviceRef deviceRef) {
+	return IOHIDDeviceGetIntProperty(deviceRef, CFSTR(kIOHIDProductIDKey));
+}
+
+static void onDeviceMatched(void * context, IOReturn result, void * sender, IOHIDDeviceRef device) {
+	CFArrayRef elements;
+	CFIndex elementIndex;
+	IOHIDElementRef element;
+	CFStringRef cfProductName;
+	struct Gamepad_device * deviceRecord;
+	struct Gamepad_devicePrivate * hidDeviceRecord;
+	IOHIDElementType type;
+	char * description;
+	struct Gamepad_queuedEvent queuedEvent;
+	
+	deviceRecord = malloc(sizeof(struct Gamepad_device));
+	deviceRecord->deviceID = nextDeviceID++;
+	deviceRecord->vendorID = IOHIDDeviceGetVendorID(device);
+	deviceRecord->productID = IOHIDDeviceGetProductID(device);
+	deviceRecord->numAxes = 0;
+	deviceRecord->numButtons = 0;
+	deviceRecord->eventDispatcher = EventDispatcher_create(deviceRecord);
+	devices = realloc(devices, sizeof(struct Gamepad_device *) * (numDevices + 1));
+	devices[numDevices++] = deviceRecord;
+	
+	hidDeviceRecord = malloc(sizeof(struct Gamepad_devicePrivate));
+	hidDeviceRecord->deviceRef = device;
+	hidDeviceRecord->axisElements = NULL;
+	hidDeviceRecord->buttonElements = NULL;
+	deviceRecord->privateData = hidDeviceRecord;
+	
+	cfProductName = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
+	if (cfProductName == NULL || CFGetTypeID(cfProductName) != CFStringGetTypeID()) {
+		description = malloc(strlen("[Unknown]" + 1));
+		strcpy(description, "[Unknown]");
+		
+	} else {
+		const char * cStringPtr;
+		
+		cStringPtr = CFStringGetCStringPtr(cfProductName, CFStringGetSmallestEncoding(cfProductName));
+		description = malloc(strlen(cStringPtr + 1));
+		strcpy(description, cStringPtr);
+	}
+	deviceRecord->description = description;
+	
+	elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
+	for (elementIndex = 0; elementIndex < CFArrayGetCount(elements); elementIndex++) {
+		element = (IOHIDElementRef) CFArrayGetValueAtIndex(elements, elementIndex);
+		type = IOHIDElementGetType(element);
+		
+		// All of the axis elements I've ever detected have been kIOHIDElementTypeInput_Misc. kIOHIDElementTypeInput_Axis is only included for good faith...
+		if (type == kIOHIDElementTypeInput_Misc ||
+		    type == kIOHIDElementTypeInput_Axis) {
+			
+			hidDeviceRecord->axisElements = realloc(hidDeviceRecord->axisElements, sizeof(struct HIDGamepadAxis) * (deviceRecord->numAxes + 1));
+			hidDeviceRecord->axisElements[deviceRecord->numAxes].cookie = IOHIDElementGetCookie(element);
+			hidDeviceRecord->axisElements[deviceRecord->numAxes].logicalMin = IOHIDElementGetLogicalMin(element);
+			hidDeviceRecord->axisElements[deviceRecord->numAxes].logicalMax = IOHIDElementGetLogicalMax(element);
+			hidDeviceRecord->axisElements[deviceRecord->numAxes].hasNullState = !!IOHIDElementHasNullState(element);
+			hidDeviceRecord->axisElements[deviceRecord->numAxes].isHatSwitch = IOHIDElementGetUsage(element) == kHIDUsage_GD_Hatswitch;
+			hidDeviceRecord->axisElements[deviceRecord->numAxes].isHatSwitchSecondAxis = false;
+			deviceRecord->numAxes++;
+			
+			if (hidDeviceRecord->axisElements[deviceRecord->numAxes - 1].isHatSwitch) {
+				hidDeviceRecord->axisElements = realloc(hidDeviceRecord->axisElements, sizeof(struct HIDGamepadAxis) * (deviceRecord->numAxes + 1));
+				hidDeviceRecord->axisElements[deviceRecord->numAxes].isHatSwitchSecondAxis = true;
+				deviceRecord->numAxes++;
+			}
+			
+		} else if (type == kIOHIDElementTypeInput_Button) {
+			hidDeviceRecord->buttonElements = realloc(hidDeviceRecord->buttonElements, sizeof(struct HIDGamepadButton) * (deviceRecord->numButtons + 1));
+			hidDeviceRecord->buttonElements[deviceRecord->numButtons].cookie = IOHIDElementGetCookie(element);
+			deviceRecord->numButtons++;
+		}
+	}
+	CFRelease(elements);
+	
+	deviceRecord->axisStates = calloc(sizeof(float), deviceRecord->numAxes);
+	deviceRecord->buttonStates = calloc(sizeof(bool), deviceRecord->numButtons);
+	
+	IOHIDDeviceRegisterInputValueCallback(device, onDeviceValueChanged, deviceRecord);
+	
+	queuedEvent.dispatcher = Gamepad_eventDispatcher();
+	queuedEvent.eventType = GAMEPAD_EVENT_DEVICE_ATTACHED;
+	queuedEvent.eventData = deviceRecord;
+	
+	if (deviceEventCount >= deviceEventQueueSize) {
+		deviceEventQueueSize = deviceEventQueueSize == 0 ? 1 : deviceEventQueueSize * 2;
+		deviceEventQueue = realloc(deviceEventQueue, sizeof(struct Gamepad_queuedEvent) * deviceEventQueueSize);
+	}
+	deviceEventQueue[deviceEventCount++] = queuedEvent;
+}
+
+static void disposeDevice(struct Gamepad_device * deviceRecord) {
+	unsigned int inputEventIndex, deviceEventIndex;
+	
+	IOHIDDeviceRegisterInputValueCallback(((struct Gamepad_devicePrivate *) deviceRecord->privateData)->deviceRef, NULL, NULL);
+	
+	for (inputEventIndex = 0; inputEventIndex < inputEventCount; inputEventIndex++) {
+		if (inputEventQueue[inputEventIndex].dispatcher == deviceRecord->eventDispatcher) {
+			unsigned int inputEventIndex2;
+			
+			free(inputEventQueue[inputEventIndex].eventData);
+			inputEventCount--;
+			for (inputEventIndex2 = inputEventIndex; inputEventIndex2 < inputEventCount; inputEventIndex2++) {
+				inputEventQueue[inputEventIndex2] = inputEventQueue[inputEventIndex2 + 1];
+			}
+			inputEventIndex--;
+		}
+	}
+	
+	for (deviceEventIndex = 0; deviceEventIndex < deviceEventCount; deviceEventIndex++) {
+		if (deviceEventQueue[deviceEventIndex].dispatcher == deviceRecord->eventDispatcher) {
+			unsigned int deviceEventIndex2;
+			
+			deviceEventCount--;
+			for (deviceEventIndex2 = deviceEventIndex; deviceEventIndex2 < deviceEventCount; deviceEventIndex2++) {
+				deviceEventQueue[deviceEventIndex2] = deviceEventQueue[deviceEventIndex2 + 1];
+			}
+			deviceEventIndex--;
+		}
+	}
+	
+	deviceRecord->eventDispatcher->dispose(deviceRecord->eventDispatcher);
+	
+	free(((struct Gamepad_devicePrivate *) deviceRecord->privateData)->axisElements);
+	free(((struct Gamepad_devicePrivate *) deviceRecord->privateData)->buttonElements);
+	free(deviceRecord->privateData);
+	
+	free((void *) deviceRecord->description);
+	free(deviceRecord->axisStates);
+	free(deviceRecord->buttonStates);
+	free(deviceRecord->eventDispatcher);
+	
+	free(deviceRecord);
+}
+
+static void onDeviceRemoved(void * context, IOReturn result, void * sender, IOHIDDeviceRef device) {
+	unsigned int deviceIndex;
+	
+	for (deviceIndex = 0; deviceIndex < numDevices; deviceIndex++) {
+		if (((struct Gamepad_devicePrivate *) devices[deviceIndex]->privateData)->deviceRef == device) {
+			Gamepad_eventDispatcher()->dispatchEvent(Gamepad_eventDispatcher(), GAMEPAD_EVENT_DEVICE_REMOVED, devices[deviceIndex]);
+			
+			disposeDevice(devices[deviceIndex]);
+			numDevices--;
+			for (; deviceIndex < numDevices; deviceIndex++) {
+				devices[deviceIndex] = devices[deviceIndex + 1];
+			}
+			return;
+		}
+	}
+}
+
+void Gamepad_init() {
+	if (hidManager == NULL) {
+		CFStringRef keys[2];
+		int value;
+		CFNumberRef values[2];
+		CFDictionaryRef dictionaries[3];
+		CFArrayRef array;
+		
+		hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
+		IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone);
+		IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+		
+		keys[0] = CFSTR(kIOHIDDeviceUsagePageKey);
+		keys[1] = CFSTR(kIOHIDDeviceUsageKey);
+		
+		value = kHIDPage_GenericDesktop;
+		values[0] = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
+		value = kHIDUsage_GD_Joystick;
+		values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
+		dictionaries[0] = CFDictionaryCreate(kCFAllocatorDefault, (const void **) keys, (const void **) values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+		CFRelease(values[0]);
+		CFRelease(values[1]);
+		
+		value = kHIDPage_GenericDesktop;
+		values[0] = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
+		value = kHIDUsage_GD_GamePad;
+		values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
+		dictionaries[1] = CFDictionaryCreate(kCFAllocatorDefault, (const void **) keys, (const void **) values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+		CFRelease(values[0]);
+		CFRelease(values[1]);
+		
+		value = kHIDPage_GenericDesktop;
+		values[0] = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
+		value = kHIDUsage_GD_MultiAxisController;
+		values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
+		dictionaries[2] = CFDictionaryCreate(kCFAllocatorDefault, (const void **) keys, (const void **) values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+		CFRelease(values[0]);
+		CFRelease(values[1]);
+		
+		array = CFArrayCreate(kCFAllocatorDefault, (const void **) dictionaries, 3, &kCFTypeArrayCallBacks);
+		CFRelease(dictionaries[0]);
+		CFRelease(dictionaries[1]);
+		CFRelease(dictionaries[2]);
+		IOHIDManagerSetDeviceMatchingMultiple(hidManager, array);
+		CFRelease(array);
+		
+		IOHIDManagerRegisterDeviceMatchingCallback(hidManager, onDeviceMatched, NULL);
+		IOHIDManagerRegisterDeviceRemovalCallback(hidManager, onDeviceRemoved, NULL);
+	}
+}
+
+void Gamepad_shutdown() {
+	if (hidManager != NULL) {
+		unsigned int deviceIndex;
+		
+		IOHIDManagerUnscheduleFromRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+		IOHIDManagerClose(hidManager, 0);
+		CFRelease(hidManager);
+		hidManager = NULL;
+		
+		for (deviceIndex = 0; deviceIndex < numDevices; deviceIndex++) {
+			disposeDevice(devices[deviceIndex]);
+		}
+		free(devices);
+		devices = NULL;
+		numDevices = 0;
+		
+		if (eventDispatcher != NULL) {
+			eventDispatcher->dispose(eventDispatcher);
+			free(eventDispatcher);
+			eventDispatcher = NULL;
+		}
+	}
+}
+
+EventDispatcher * Gamepad_eventDispatcher() {
+	if (eventDispatcher == NULL) {
+		eventDispatcher = EventDispatcher_create(NULL);
+	}
+	return eventDispatcher;
+}
+
+unsigned int Gamepad_numDevices() {
+	return numDevices;
+}
+
+struct Gamepad_device * Gamepad_deviceAtIndex(unsigned int deviceIndex) {
+	if (deviceIndex >= numDevices) {
+		return NULL;
+	}
+	return devices[deviceIndex];
+}
+
+void Gamepad_detectDevices() {
+	unsigned int eventIndex;
+	
+	if (hidManager == NULL) {
+		return;
+	}
+	
+	for (eventIndex = 0; eventIndex < deviceEventCount; eventIndex++) {
+		deviceEventQueue[eventIndex].dispatcher->dispatchEvent(deviceEventQueue[eventIndex].dispatcher, deviceEventQueue[eventIndex].eventType, deviceEventQueue[eventIndex].eventData);
+	}
+	deviceEventCount = 0;
+}
+
+void Gamepad_processEvents() {
+	unsigned int eventIndex;
+	
+	if (hidManager == NULL) {
+		return;
+	}
+	
+	for (eventIndex = 0; eventIndex < inputEventCount; eventIndex++) {
+		inputEventQueue[eventIndex].dispatcher->dispatchEvent(inputEventQueue[eventIndex].dispatcher, inputEventQueue[eventIndex].eventType, inputEventQueue[eventIndex].eventData);
+		free(inputEventQueue[eventIndex].eventData);
+	}
+	inputEventCount = 0;
+}
+

+ 416 - 0
Dependencies/Local/libstem_gamepad/gamepad/Gamepad_windows.c

@@ -0,0 +1,416 @@
+/*
+  Copyright (c) 2010 Alex Diener
+  
+  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.
+  
+  Alex Diener [email protected]
+*/
+
+#include "gamepad/Gamepad.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+#include <regstr.h>
+
+struct Gamepad_devicePrivate {
+	UINT joystickID;
+	JOYINFOEX lastState;
+	int xAxisIndex;
+	int yAxisIndex;
+	int zAxisIndex;
+	int rAxisIndex;
+	int uAxisIndex;
+	int vAxisIndex;
+	int povXAxisIndex;
+	int povYAxisIndex;
+	UINT (* axisRanges)[2];
+};
+
+static struct Gamepad_device ** devices = NULL;
+static unsigned int numDevices = 0;
+static unsigned int nextDeviceID = 0;
+
+static EventDispatcher * eventDispatcher = NULL;
+static bool inited = false;
+
+void Gamepad_init() {
+	if (!inited) {
+		inited = true;
+		Gamepad_detectDevices();
+	}
+}
+
+static void disposeDevice(struct Gamepad_device * deviceRecord) {
+	deviceRecord->eventDispatcher->dispose(deviceRecord->eventDispatcher);
+	
+	free(((struct Gamepad_devicePrivate *) deviceRecord->privateData)->axisRanges);
+	free(deviceRecord->privateData);
+	
+	free((void *) deviceRecord->description);
+	free(deviceRecord->axisStates);
+	free(deviceRecord->buttonStates);
+	free(deviceRecord->eventDispatcher);
+	
+	free(deviceRecord);
+}
+
+void Gamepad_shutdown() {
+	unsigned int deviceIndex;
+	
+	if (inited) {
+		for (deviceIndex = 0; deviceIndex < numDevices; deviceIndex++) {
+			disposeDevice(devices[deviceIndex]);
+		}
+		free(devices);
+		devices = NULL;
+		numDevices = 0;
+		if (eventDispatcher != NULL) {
+			eventDispatcher->dispose(eventDispatcher);
+			free(eventDispatcher);
+			eventDispatcher = NULL;
+		}
+		inited = false;
+	}
+}
+
+EventDispatcher * Gamepad_eventDispatcher() {
+	if (eventDispatcher == NULL) {
+		eventDispatcher = EventDispatcher_create(NULL);
+	}
+	return eventDispatcher;
+}
+
+unsigned int Gamepad_numDevices() {
+	return numDevices;
+}
+
+struct Gamepad_device * Gamepad_deviceAtIndex(unsigned int deviceIndex) {
+	if (deviceIndex >= numDevices) {
+		return NULL;
+	}
+	return devices[deviceIndex];
+}
+
+#define REG_STRING_MAX 256
+
+static char * getDeviceDescription(UINT joystickID, JOYCAPS caps) {
+	char * description;
+	char subkey[REG_STRING_MAX];
+	HKEY topKey, key;
+	LONG result;
+	
+	snprintf(subkey, REG_STRING_MAX, "%s\\%s\\%s", REGSTR_PATH_JOYCONFIG, caps.szRegKey, REGSTR_KEY_JOYCURR);
+	result = RegOpenKeyEx(topKey = HKEY_LOCAL_MACHINE, subkey, 0, KEY_READ, &key);
+	if (result != ERROR_SUCCESS) {
+		result = RegOpenKeyEx(topKey = HKEY_CURRENT_USER, subkey, 0, KEY_READ, &key);
+	}
+	if (result == ERROR_SUCCESS) {
+		char value[REG_STRING_MAX];
+		char name[REG_STRING_MAX];
+		DWORD nameSize;
+		
+		snprintf(value, REG_STRING_MAX, "Joystick%d%s", joystickID + 1, REGSTR_VAL_JOYOEMNAME);
+		nameSize = sizeof(name);
+		result = RegQueryValueEx(key, value, NULL, NULL, (LPBYTE) name, &nameSize);
+		RegCloseKey(key);
+		
+		if (result == ERROR_SUCCESS) {
+			snprintf(subkey, REG_STRING_MAX, "%s\\%s", REGSTR_PATH_JOYOEM, name);
+			result = RegOpenKeyEx(topKey, subkey, 0, KEY_READ, &key);
+			
+			if (result == ERROR_SUCCESS) {
+				nameSize = sizeof(name);
+				result = RegQueryValueEx(key, REGSTR_VAL_JOYOEMNAME, NULL, NULL, NULL, &nameSize);
+				
+				if (result == ERROR_SUCCESS) {
+					description = malloc(nameSize);
+					result = RegQueryValueEx(key, REGSTR_VAL_JOYOEMNAME, NULL, NULL, (LPBYTE) description, &nameSize);
+				}
+				RegCloseKey(key);
+				
+				if (result == ERROR_SUCCESS) {
+					return description;
+				}
+				free(description);
+			}
+		}
+	}
+	
+	description = malloc(strlen(caps.szPname) + 1);
+	strcpy(description, caps.szPname);
+	
+	return description;
+}
+
+void Gamepad_detectDevices() {
+	unsigned int numPadsSupported;
+	unsigned int deviceIndex, deviceIndex2;
+	JOYINFOEX info;
+	JOYCAPS caps;
+	bool duplicate;
+	struct Gamepad_device * deviceRecord;
+	struct Gamepad_devicePrivate * deviceRecordPrivate;
+	UINT joystickID;
+	int axisIndex;
+	
+	if (!inited) {
+		return;
+	}
+	
+	numPadsSupported = joyGetNumDevs();
+	for (deviceIndex = 0; deviceIndex < numPadsSupported; deviceIndex++) {
+		info.dwSize = sizeof(info);
+		info.dwFlags = JOY_RETURNALL;
+		joystickID = JOYSTICKID1 + deviceIndex;
+		if (joyGetPosEx(joystickID, &info) == JOYERR_NOERROR &&
+		    joyGetDevCaps(joystickID, &caps, sizeof(JOYCAPS)) == JOYERR_NOERROR) {
+			
+			duplicate = false;
+			for (deviceIndex2 = 0; deviceIndex2 < numDevices; deviceIndex2++) {
+				if (((struct Gamepad_devicePrivate *) devices[deviceIndex2]->privateData)->joystickID == joystickID) {
+					duplicate = true;
+					break;
+				}
+			}
+			if (duplicate) {
+				continue;
+			}
+			
+			deviceRecord = malloc(sizeof(struct Gamepad_device));
+			deviceRecord->deviceID = nextDeviceID++;
+			deviceRecord->description = getDeviceDescription(joystickID, caps);
+			deviceRecord->vendorID = caps.wMid;
+			deviceRecord->productID = caps.wPid;
+			deviceRecord->numAxes = caps.wNumAxes + ((caps.wCaps & JOYCAPS_HASPOV) ? 2 : 0);
+			deviceRecord->numButtons = caps.wNumButtons;
+			deviceRecord->axisStates = calloc(sizeof(float), deviceRecord->numAxes);
+			deviceRecord->buttonStates = calloc(sizeof(bool), deviceRecord->numButtons);
+			deviceRecord->eventDispatcher = EventDispatcher_create(deviceRecord);
+			devices = realloc(devices, sizeof(struct Gamepad_device *) * (numDevices + 1));
+			devices[numDevices++] = deviceRecord;
+			
+			deviceRecordPrivate = malloc(sizeof(struct Gamepad_devicePrivate));
+			deviceRecordPrivate->joystickID = joystickID;
+			deviceRecordPrivate->lastState = info;
+			
+			deviceRecordPrivate->xAxisIndex = 0;
+			deviceRecordPrivate->yAxisIndex = 1;
+			axisIndex = 2;
+			deviceRecordPrivate->zAxisIndex = (caps.wCaps & JOYCAPS_HASZ) ? axisIndex++ : -1;
+			deviceRecordPrivate->rAxisIndex = (caps.wCaps & JOYCAPS_HASR) ? axisIndex++ : -1;
+			deviceRecordPrivate->uAxisIndex = (caps.wCaps & JOYCAPS_HASU) ? axisIndex++ : -1;
+			deviceRecordPrivate->vAxisIndex = (caps.wCaps & JOYCAPS_HASV) ? axisIndex++ : -1;
+			
+			deviceRecordPrivate->axisRanges = malloc(sizeof(UINT[2]) * axisIndex);
+			deviceRecordPrivate->axisRanges[0][0] = caps.wXmin;
+			deviceRecordPrivate->axisRanges[0][1] = caps.wXmax;
+			deviceRecordPrivate->axisRanges[1][0] = caps.wYmin;
+			deviceRecordPrivate->axisRanges[1][1] = caps.wYmax;
+			if (deviceRecordPrivate->zAxisIndex != -1) {
+				deviceRecordPrivate->axisRanges[deviceRecordPrivate->zAxisIndex][0] = caps.wZmin;
+				deviceRecordPrivate->axisRanges[deviceRecordPrivate->zAxisIndex][1] = caps.wZmax;
+			}
+			if (deviceRecordPrivate->rAxisIndex != -1) {
+				deviceRecordPrivate->axisRanges[deviceRecordPrivate->rAxisIndex][0] = caps.wRmin;
+				deviceRecordPrivate->axisRanges[deviceRecordPrivate->rAxisIndex][1] = caps.wRmax;
+			}
+			if (deviceRecordPrivate->uAxisIndex != -1) {
+				deviceRecordPrivate->axisRanges[deviceRecordPrivate->uAxisIndex][0] = caps.wUmin;
+				deviceRecordPrivate->axisRanges[deviceRecordPrivate->uAxisIndex][1] = caps.wUmax;
+			}
+			if (deviceRecordPrivate->vAxisIndex != -1) {
+				deviceRecordPrivate->axisRanges[deviceRecordPrivate->vAxisIndex][0] = caps.wVmin;
+				deviceRecordPrivate->axisRanges[deviceRecordPrivate->vAxisIndex][1] = caps.wVmax;
+			}
+			
+			deviceRecordPrivate->povXAxisIndex = (caps.wCaps & JOYCAPS_HASPOV) ? axisIndex++ : -1;
+			deviceRecordPrivate->povYAxisIndex = (caps.wCaps & JOYCAPS_HASPOV) ? axisIndex++ : -1;
+			
+			deviceRecord->privateData = deviceRecordPrivate;
+			
+			Gamepad_eventDispatcher()->dispatchEvent(Gamepad_eventDispatcher(), GAMEPAD_EVENT_DEVICE_ATTACHED, deviceRecord);
+		}
+	}
+}
+
+static double currentTime() {
+	// HACK: No timestamp data from joyGetInfoEx, so we make it up
+	static LARGE_INTEGER frequency;
+	LARGE_INTEGER currentTime;
+	
+	if (frequency.QuadPart == 0) {
+		QueryPerformanceFrequency(&frequency);
+	}
+	QueryPerformanceCounter(&currentTime);
+	
+	return (double) currentTime.QuadPart / frequency.QuadPart;
+}
+
+static void handleAxisChange(struct Gamepad_device * device, int axisIndex, DWORD value) {
+	struct Gamepad_axisEvent axisEvent;
+	struct Gamepad_devicePrivate * devicePrivate;
+	
+	if (axisIndex < 0 || axisIndex >= (int) device->numAxes) {
+		return;
+	}
+	
+	devicePrivate = device->privateData;
+	
+	axisEvent.device = device;
+	axisEvent.timestamp = currentTime();
+	axisEvent.axisID = axisIndex;
+	axisEvent.value = (value - devicePrivate->axisRanges[axisIndex][0]) / (float) (devicePrivate->axisRanges[axisIndex][1] - devicePrivate->axisRanges[axisIndex][0]) * 2.0f - 1.0f;
+	
+	device->axisStates[axisIndex] = axisEvent.value;
+	device->eventDispatcher->dispatchEvent(device->eventDispatcher, GAMEPAD_EVENT_AXIS_MOVED, &axisEvent);
+}
+
+static void handleButtonChange(struct Gamepad_device * device, DWORD lastValue, DWORD value) {
+	struct Gamepad_buttonEvent buttonEvent;
+	unsigned int buttonIndex;
+	
+	for (buttonIndex = 0; buttonIndex < device->numButtons; buttonIndex++) {
+		if ((lastValue ^ value) & (1 << buttonIndex)) {
+			buttonEvent.device = device;
+			buttonEvent.timestamp = currentTime();
+			buttonEvent.buttonID = buttonIndex;
+			buttonEvent.down = !!(value & (1 << buttonIndex));
+			
+			device->buttonStates[buttonIndex] = buttonEvent.down;
+			device->eventDispatcher->dispatchEvent(device->eventDispatcher, buttonEvent.down ? GAMEPAD_EVENT_BUTTON_DOWN : GAMEPAD_EVENT_BUTTON_UP, &buttonEvent);
+		}
+	}
+}
+
+static void povToXY(DWORD pov, int * outX, int * outY) {
+	if (pov == JOY_POVCENTERED) {
+		*outX = *outY = 0;
+		
+	} else {
+		if (pov > JOY_POVFORWARD && pov < JOY_POVBACKWARD) {
+			*outX = 1;
+			
+		} else if (pov > JOY_POVBACKWARD) {
+			*outX = -1;
+			
+		} else {
+			*outX = 0;
+		}
+		
+		if (pov > JOY_POVLEFT || pov < JOY_POVRIGHT) {
+			*outY = -1;
+			
+		} else if (pov > JOY_POVRIGHT && pov < JOY_POVLEFT) {
+			*outY = 1;
+			
+		} else {
+			*outY = 0;
+		}
+	}
+}
+
+static void handlePOVChange(struct Gamepad_device * device, DWORD lastValue, DWORD value) {
+	struct Gamepad_devicePrivate * devicePrivate;
+	int lastX, lastY, newX, newY;
+	struct Gamepad_axisEvent axisEvent;
+	
+	devicePrivate = device->privateData;
+	
+	if (devicePrivate->povXAxisIndex == -1 || devicePrivate->povYAxisIndex == -1) {
+		return;
+	}
+	
+	povToXY(lastValue, &lastX, &lastY);
+	povToXY(value, &newX, &newY);
+	
+	if (newX != lastX) {
+		axisEvent.device = device;
+		axisEvent.timestamp = currentTime();
+		axisEvent.axisID = devicePrivate->povXAxisIndex;
+		axisEvent.value = newX;
+		
+		device->axisStates[devicePrivate->povXAxisIndex] = axisEvent.value;
+		device->eventDispatcher->dispatchEvent(device->eventDispatcher, GAMEPAD_EVENT_AXIS_MOVED, &axisEvent);
+	}
+	if (newY != lastY) {
+		axisEvent.device = device;
+		axisEvent.timestamp = currentTime();
+		axisEvent.axisID = devicePrivate->povYAxisIndex;
+		axisEvent.value = newY;
+		
+		device->axisStates[devicePrivate->povYAxisIndex] = axisEvent.value;
+		device->eventDispatcher->dispatchEvent(device->eventDispatcher, GAMEPAD_EVENT_AXIS_MOVED, &axisEvent);
+	}
+}
+
+void Gamepad_processEvents() {
+	unsigned int deviceIndex;
+	JOYINFOEX info;
+	MMRESULT result;
+	struct Gamepad_device * device;
+	struct Gamepad_devicePrivate * devicePrivate;
+	
+	if (!inited) {
+		return;
+	}
+	
+	for (deviceIndex = 0; deviceIndex < numDevices; deviceIndex++) {
+		device = devices[deviceIndex];
+		devicePrivate = device->privateData;
+		
+		info.dwSize = sizeof(info);
+		info.dwFlags = JOY_RETURNALL;
+		result = joyGetPosEx(devicePrivate->joystickID, &info);
+		if (result == JOYERR_UNPLUGGED) {
+			Gamepad_eventDispatcher()->dispatchEvent(Gamepad_eventDispatcher(), GAMEPAD_EVENT_DEVICE_REMOVED, device);
+			
+			disposeDevice(device);
+			numDevices--;
+			for (; deviceIndex < numDevices; deviceIndex++) {
+				devices[deviceIndex] = devices[deviceIndex + 1];
+			}
+			
+		} else if (result == JOYERR_NOERROR) {
+			if (info.dwXpos != devicePrivate->lastState.dwXpos) {
+				handleAxisChange(device, devicePrivate->xAxisIndex, info.dwXpos);
+			}
+			if (info.dwYpos != devicePrivate->lastState.dwYpos) {
+				handleAxisChange(device, devicePrivate->yAxisIndex, info.dwYpos);
+			}
+			if (info.dwZpos != devicePrivate->lastState.dwZpos) {
+				handleAxisChange(device, devicePrivate->zAxisIndex, info.dwZpos);
+			}
+			if (info.dwRpos != devicePrivate->lastState.dwRpos) {
+				handleAxisChange(device, devicePrivate->rAxisIndex, info.dwRpos);
+			}
+			if (info.dwUpos != devicePrivate->lastState.dwUpos) {
+				handleAxisChange(device, devicePrivate->uAxisIndex, info.dwUpos);
+			}
+			if (info.dwVpos != devicePrivate->lastState.dwVpos) {
+				handleAxisChange(device, devicePrivate->vAxisIndex, info.dwVpos);
+			}
+			if (info.dwPOV != devicePrivate->lastState.dwPOV) {
+				handlePOVChange(device, devicePrivate->lastState.dwPOV, info.dwPOV);
+			}
+			if (info.dwButtons != devicePrivate->lastState.dwButtons) {
+				handleButtonChange(device, devicePrivate->lastState.dwButtons, info.dwButtons);
+			}
+			devicePrivate->lastState = info;
+		}
+	}
+}
+

+ 122 - 0
Dependencies/Local/libstem_gamepad/utilities/EventDispatcher.c

@@ -0,0 +1,122 @@
+/*
+  Copyright (c) 2010 Alex Diener
+  
+  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.
+  
+  Alex Diener [email protected]
+*/
+
+#include "utilities/EventDispatcher.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct EventTarget {
+	char * eventID;
+	EventDispatcherCallback callback;
+	void * context;
+};
+
+EventDispatcher * EventDispatcher_create(void * owner) {
+	EventDispatcher * self;
+	
+	self = malloc(sizeof(EventDispatcher));
+	EventDispatcher_init(self, owner);
+	return self;
+}
+
+void EventDispatcher_init(EventDispatcher * self, void * owner) {
+	self->dispose = EventDispatcher_dispose;
+	self->registerForEvent = EventDispatcher_registerForEvent;
+	self->unregisterForEvent = EventDispatcher_unregisterForEvent;
+	self->dispatchEvent = EventDispatcher_dispatchEvent;
+	
+	self->owner = owner;
+	self->numberOfTargets = 0;
+	self->targetListSize = 1;
+	self->targets = (struct EventTarget *) malloc(sizeof(struct EventTarget) * self->targetListSize);
+}
+
+void EventDispatcher_dispose(void * selfPtr) {
+	EventDispatcher * self = selfPtr;
+	int targetIndex;
+	
+	for (targetIndex = 0; targetIndex < self->numberOfTargets; targetIndex++) {
+		free(self->targets[targetIndex].eventID);
+	}
+	free(self->targets);
+}
+
+void EventDispatcher_registerForEvent(void * selfPtr, const char * eventID, EventDispatcherCallback callback, void * context) {
+	EventDispatcher * self = selfPtr;
+	size_t length;
+	
+	if (self->numberOfTargets >= self->targetListSize) {
+		self->targetListSize *= 2;
+		self->targets = (struct EventTarget *) realloc(self->targets, sizeof(struct EventTarget) * self->targetListSize);
+	}
+	
+	length = strlen(eventID);
+	self->targets[self->numberOfTargets].eventID = malloc(length + 1);
+	strncpy(self->targets[self->numberOfTargets].eventID, eventID, length + 1);
+	self->targets[self->numberOfTargets].callback = callback;
+	self->targets[self->numberOfTargets].context = context;
+	self->numberOfTargets++;
+}
+
+void EventDispatcher_unregisterForEvent(void * selfPtr, const char * eventID, EventDispatcherCallback callback) {
+	EventDispatcher * self = selfPtr;
+	int targetIndex;
+	
+	for (targetIndex = 0; targetIndex < self->numberOfTargets; targetIndex++) {
+		if (!strcmp(eventID, self->targets[targetIndex].eventID) && self->targets[targetIndex].callback == callback) {
+			free(self->targets[targetIndex].eventID);
+			self->numberOfTargets--;
+			for (; targetIndex < self->numberOfTargets; targetIndex++) {
+				self->targets[targetIndex] = self->targets[targetIndex + 1];
+			}
+			break;
+		}
+	}
+}
+
+bool EventDispatcher_dispatchEvent(void * selfPtr, const char * eventID, void * eventData) {
+	EventDispatcher * self = selfPtr;
+	int targetIndex;
+	int numberOfTargetsCopy;
+	struct EventTarget * targetsCopy;
+	bool eventHandled, anyEventsHandled;
+	
+	numberOfTargetsCopy = self->numberOfTargets;
+	targetsCopy = malloc(sizeof(struct EventTarget) * numberOfTargetsCopy);
+	memcpy(targetsCopy, self->targets, sizeof(struct EventTarget) * numberOfTargetsCopy);
+	
+	anyEventsHandled = false;
+	for (targetIndex = 0; targetIndex < numberOfTargetsCopy; targetIndex++) {
+		if (!strcmp(eventID, self->targets[targetIndex].eventID)) {
+			eventHandled = targetsCopy[targetIndex].callback(self->owner, eventID, eventData, targetsCopy[targetIndex].context);
+			
+			if (eventHandled) {
+				anyEventsHandled = true;
+			}
+		}
+	}
+	
+	free(targetsCopy);
+	
+	return anyEventsHandled;
+}

+ 80 - 0
Dependencies/Local/libstem_gamepad/utilities/EventDispatcher.h

@@ -0,0 +1,80 @@
+/*
+  Copyright (c) 2010 Alex Diener
+  
+  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.
+  
+  Alex Diener [email protected]
+*/
+
+#ifndef __EVENT_DISPATCHER_H__
+#define __EVENT_DISPATCHER_H__
+
+#include <stdbool.h>
+
+typedef struct EventDispatcher EventDispatcher;
+
+/* Signature for event handler callbacks.
+   
+   sender: Object that dispatched the event. More specifically, the object passed to EventDispatcher_create.
+   eventID: Name of event that was triggered
+   eventData: Arbitrary data passed by dispatcher. Its format is known by convention depending on the event ID being dispatched.
+   context: Value passed as context to registerForEvent
+   
+   This function should return true if the event was handled, or false if it was ignored. */
+typedef bool (* EventDispatcherCallback)(void * sender, const char * eventID, void * eventData, void * context);
+
+struct EventTarget;
+
+#define EventDispatcher_structContents \
+	void * owner; \
+	\
+	int numberOfTargets; \
+	int targetListSize; \
+	struct EventTarget * targets; \
+	\
+	void (* dispose)(void * self); \
+	void (* registerForEvent)(void * self, const char * eventID, EventDispatcherCallback callback, void * context); \
+	void (* unregisterForEvent)(void * self, const char * eventID, EventDispatcherCallback callback); \
+	bool (* dispatchEvent)(void * self, const char * eventID, void * eventData);
+
+struct EventDispatcher {
+	EventDispatcher_structContents
+};
+
+/* Allocate and initialize a new EventDispatcher object. owner will be passed to event callbacks as
+   the sender parameter. */
+EventDispatcher * EventDispatcher_create(void * owner);
+
+/* Initialize an already allocated EventDispatcher. owner will be passed to event callbacks as the
+   sender parameter. */
+void EventDispatcher_init(EventDispatcher * self, void * owner);
+
+/* Free all memory allocated by EventDispatcher and remove all registered listeners. Does NOT free
+   the EventDispatcher itself. */
+void EventDispatcher_dispose(void * selfPtr);
+
+/* Register for notification of events of type eventID */
+void EventDispatcher_registerForEvent(void * selfPtr, const char * eventID, EventDispatcherCallback callback, void * context);
+
+/* Remove a previous registration for events of type eventID */
+void EventDispatcher_unregisterForEvent(void * selfPtr, const char * eventID, EventDispatcherCallback callback);
+
+/* Dispatch an event to all registered listeners for that event ID. Returns true if any listener is
+   registered and returns true from its handler callback. */
+bool EventDispatcher_dispatchEvent(void * selfPtr, const char * eventID, void * eventData);
+
+#endif

BIN
Player/Build/Mac OS X/Polycode Player.xcodeproj/project.xcworkspace/xcuserdata/ivansafrin.xcuserdatad/UserInterfaceState.xcuserstate