aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--src/library_sdl.js234
-rw-r--r--src/struct_info.json15
-rw-r--r--tests/sdl_joystick.c123
-rw-r--r--tests/test_browser.py76
5 files changed, 435 insertions, 14 deletions
diff --git a/AUTHORS b/AUTHORS
index 0b8fdf33..6a28c205 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -107,3 +107,4 @@ a license to everyone to use it as detailed in LICENSE.)
* Michael Tirado <icetooth333@gmail.com>
* Ben Noordhuis <info@bnoordhuis.nl>
* Bob Roberts <bobroberts177@gmail.com>
+* John Vilk <jvilk@cs.umass.edu>
diff --git a/src/library_sdl.js b/src/library_sdl.js
index 5b43b7ab..c46364ff 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -75,6 +75,7 @@ var LibrarySDL = {
textInput: false,
startTime: null,
+ initFlags: 0, // The flags passed to SDL_Init
buttonState: 0,
modState: 0,
DOMButtons: [0, 0, 0],
@@ -639,6 +640,21 @@ var LibrarySDL = {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.h, 'event.h', 'i32') }}};
break;
}
+ case 'joystick_button_up': case 'joystick_button_down': {
+ var state = event.type === 'joystick_button_up' ? 0 : 1;
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.which, 'event.index', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.button, 'event.button', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.state, 'state', 'i8') }}};
+ break;
+ }
+ case 'joystick_axis_motion': {
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.which, 'event.index', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.axis, 'event.axis', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}};
+ break;
+ }
default: throw 'Unhandled SDL event: ' + event.type;
}
},
@@ -695,7 +711,109 @@ var LibrarySDL = {
for (var i = 0; i < num; i++) {
console.log(' diagonal ' + i + ':' + [data[i*surfData.width*4 + i*4 + 0], data[i*surfData.width*4 + i*4 + 1], data[i*surfData.width*4 + i*4 + 2], data[i*surfData.width*4 + i*4 + 3]]);
}
- }
+ },
+
+ // Joystick helper methods and state
+
+ joystickEventState: 0,
+ lastJoystickState: {}, // Map from SDL_Joystick* to their last known state. Required to determine if a change has occurred.
+ // Maps Joystick names to pointers. Allows us to avoid reallocating memory for
+ // joystick names each time this function is called.
+ joystickNamePool: {},
+ recordJoystickState: function(joystick, state) {
+ // Standardize button state.
+ var buttons = new Array(state.buttons.length);
+ for (var i = 0; i < state.buttons.length; i++) {
+ buttons[i] = SDL.getJoystickButtonState(state.buttons[i]);
+ }
+
+ SDL.lastJoystickState[joystick] = {
+ buttons: buttons,
+ axes: state.axes.slice(0),
+ timestamp: state.timestamp,
+ index: state.index,
+ id: state.id
+ };
+ },
+ // Retrieves the button state of the given gamepad button.
+ // Abstracts away implementation differences.
+ // Returns 'true' if pressed, 'false' otherwise.
+ getJoystickButtonState: function(button) {
+ if (typeof button === 'object') {
+ // Current gamepad API editor's draft (Firefox Nightly)
+ // https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-GamepadButton
+ return button.pressed;
+ } else {
+ // Current gamepad API working draft (Firefox / Chrome Stable)
+ // http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface
+ return button > 0;
+ }
+ },
+ // Queries for and inserts controller events into the SDL queue.
+ queryJoysticks: function() {
+ for (var joystick in SDL.lastJoystickState) {
+ var state = SDL.getGamepad(joystick - 1);
+ var prevState = SDL.lastJoystickState[joystick];
+ // Check only if the timestamp has differed.
+ // NOTE: Timestamp is not available in Firefox.
+ if (typeof state.timestamp !== 'number' || state.timestamp !== prevState.timestamp) {
+ var i;
+ for (i = 0; i < state.buttons.length; i++) {
+ var buttonState = SDL.getJoystickButtonState(state.buttons[i]);
+ // NOTE: The previous state already has a boolean representation of
+ // its button, so no need to standardize its button state here.
+ if (buttonState !== prevState.buttons[i]) {
+ // Insert button-press event.
+ SDL.events.push({
+ type: buttonState ? 'joystick_button_down' : 'joystick_button_up',
+ joystick: joystick,
+ index: joystick - 1,
+ button: i
+ });
+ }
+ }
+ for (i = 0; i < state.axes.length; i++) {
+ if (state.axes[i] !== prevState.axes[i]) {
+ // Insert axes-change event.
+ SDL.events.push({
+ type: 'joystick_axis_motion',
+ joystick: joystick,
+ index: joystick - 1,
+ axis: i,
+ value: state.axes[i]
+ });
+ }
+ }
+
+ SDL.recordJoystickState(joystick, state);
+ }
+ }
+ },
+ // Converts the double-based browser axis value [-1, 1] into SDL's 16-bit
+ // value [-32768, 32767]
+ joystickAxisValueConversion: function(value) {
+ // Ensures that 0 is 0, 1 is 32767, and -1 is 32768.
+ return Math.ceil(((value+1) * 32767.5) - 32768);
+ },
+
+ getGamepads: function() {
+ var fcn = navigator.getGamepads || navigator.webkitGamepads || navigator.mozGamepads || navigator.gamepads || navigator.webkitGetGamepads;
+ if (fcn !== undefined) {
+ // The function must be applied on the navigator object.
+ return fcn.apply(navigator);
+ } else {
+ return [];
+ }
+ },
+
+ // Helper function: Returns the gamepad if available, or null if not.
+ getGamepad: function(deviceIndex) {
+ var gamepads = SDL.getGamepads();
+ if (gamepads.length > deviceIndex && deviceIndex >= 0) {
+ return gamepads[deviceIndex];
+ }
+ return null;
+ },
},
SDL_Linked_Version: function() {
@@ -708,8 +826,10 @@ var LibrarySDL = {
return SDL.version;
},
- SDL_Init: function(what) {
+ SDL_Init: function(initFlags) {
SDL.startTime = Date.now();
+ SDL.initFlags = initFlags;
+
// capture all key events. we just keep down and up, but also capture press to prevent default actions
if (!Module['doNotCaptureKeyboard']) {
document.addEventListener("keydown", SDL.receiveEvent);
@@ -718,6 +838,15 @@ var LibrarySDL = {
window.addEventListener("blur", SDL.receiveEvent);
document.addEventListener("visibilitychange", SDL.receiveEvent);
}
+
+ if (initFlags & 0x200) {
+ // SDL_INIT_JOYSTICK
+ // Firefox will not give us Joystick data unless we register this NOP
+ // callback.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=936104
+ addEventListener("gamepadconnected", function() {});
+ }
+
window.addEventListener("unload", SDL.receiveEvent);
SDL.keyboardState = _malloc(0x10000); // Our SDL needs 512, but 64K is safe for older SDLs
_memset(SDL.keyboardState, 0, 0x10000);
@@ -730,6 +859,12 @@ var LibrarySDL = {
SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */;
SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */;
SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */;
+ // These are not technically DOM events; the HTML gamepad API is poll-based.
+ // However, we define them here, as the rest of the SDL code assumes that
+ // all SDL events originate as DOM events.
+ SDL.DOMEventToSDLEvent['joystick_axis_motion'] = 0x600 /* SDL_JOYAXISMOTION */;
+ SDL.DOMEventToSDLEvent['joystick_button_down'] = 0x603 /* SDL_JOYBUTTONDOWN */;
+ SDL.DOMEventToSDLEvent['joystick_button_up'] = 0x604 /* SDL_JOYBUTTONUP */;
return 0; // success
},
@@ -1189,6 +1324,11 @@ var LibrarySDL = {
},
SDL_PollEvent: function(ptr) {
+ if (SDL.initFlags & 0x200 && SDL.joystickEventState) {
+ // If SDL_INIT_JOYSTICK was supplied AND the joystick system is configured
+ // to automatically query for events, query for joystick events.
+ SDL.queryJoysticks();
+ }
if (SDL.events.length === 0) return 0;
if (ptr) {
SDL.makeCEvent(SDL.events.shift(), ptr);
@@ -2375,37 +2515,103 @@ var LibrarySDL = {
// Joysticks
- SDL_NumJoysticks: function() { return 0; },
+ SDL_NumJoysticks: function() {
+ var count = 0;
+ var gamepads = SDL.getGamepads();
+ // The length is not the number of gamepads; check which ones are defined.
+ for (var i = 0; i < gamepads.length; i++) {
+ if (gamepads[i] !== undefined) count++;
+ }
+ return count;
+ },
- SDL_JoystickName: function(deviceIndex) { return 0; },
+ SDL_JoystickName: function(deviceIndex) {
+ var gamepad = SDL.getGamepad(deviceIndex);
+ if (gamepad) {
+ var name = gamepad.id;
+ if (SDL.joystickNamePool.hasOwnProperty(name)) {
+ return SDL.joystickNamePool[name];
+ }
+ return SDL.joystickNamePool[name] = allocate(intArrayFromString(name), 'i8', ALLOC_NORMAL);
+ }
+ return 0;
+ },
- SDL_JoystickOpen: function(deviceIndex) { return 0; },
+ SDL_JoystickOpen: function(deviceIndex) {
+ var gamepad = SDL.getGamepad(deviceIndex);
+ if (gamepad) {
+ // Use this as a unique 'pointer' for this joystick.
+ var joystick = deviceIndex+1;
+ SDL.recordJoystickState(joystick, gamepad);
+ return joystick;
+ }
+ return 0;
+ },
- SDL_JoystickOpened: function(deviceIndex) { return 0; },
+ SDL_JoystickOpened: function(deviceIndex) {
+ return SDL.lastJoystickState.hasOwnProperty(deviceIndex+1) ? 1 : 0;
+ },
- SDL_JoystickIndex: function(joystick) { return 0; },
+ SDL_JoystickIndex: function(joystick) {
+ // joystick pointers are simply the deviceIndex+1.
+ return joystick - 1;
+ },
- SDL_JoystickNumAxes: function(joystick) { return 0; },
+ SDL_JoystickNumAxes: function(joystick) {
+ var gamepad = SDL.getGamepad(joystick - 1);
+ if (gamepad) {
+ return gamepad.axes.length;
+ }
+ return 0;
+ },
SDL_JoystickNumBalls: function(joystick) { return 0; },
SDL_JoystickNumHats: function(joystick) { return 0; },
- SDL_JoystickNumButtons: function(joystick) { return 0; },
+ SDL_JoystickNumButtons: function(joystick) {
+ var gamepad = SDL.getGamepad(joystick - 1);
+ if (gamepad) {
+ return gamepad.buttons.length;
+ }
+ return 0;
+ },
- SDL_JoystickUpdate: function() {},
+ SDL_JoystickUpdate: function() {
+ SDL.queryJoysticks();
+ },
- SDL_JoystickEventState: function(state) { return 0; },
+ SDL_JoystickEventState: function(state) {
+ if (state < 0) {
+ // SDL_QUERY: Return current state.
+ return SDL.joystickEventState;
+ }
+ return SDL.joystickEventState = state;
+ },
- SDL_JoystickGetAxis: function(joystick, axis) { return 0; },
+ SDL_JoystickGetAxis: function(joystick, axis) {
+ var gamepad = SDL.getGamepad(joystick - 1);
+ if (gamepad && gamepad.axes.length > axis) {
+ return SDL.joystickAxisValueConversion(gamepad.axes[axis]);
+ }
+ return 0;
+ },
SDL_JoystickGetHat: function(joystick, hat) { return 0; },
SDL_JoystickGetBall: function(joystick, ball, dxptr, dyptr) { return -1; },
- SDL_JoystickGetButton: function(joystick, button) { return 0; },
+ SDL_JoystickGetButton: function(joystick, button) {
+ var gamepad = SDL.getGamepad(joystick - 1);
+ if (gamepad && gamepad.buttons.length > button) {
+ return SDL.getJoystickButtonState(gamepad.buttons[button]) ? 1 : 0;
+ }
+ return 0;
+ },
- SDL_JoystickClose: function(joystick) {},
+ SDL_JoystickClose: function(joystick) {
+ delete SDL.lastJoystickState[joystick];
+ },
// Misc
diff --git a/src/struct_info.json b/src/struct_info.json
index 5b4726e8..f6499295 100644
--- a/src/struct_info.json
+++ b/src/struct_info.json
@@ -961,6 +961,21 @@
"x",
"y"
],
+ "SDL_JoyAxisEvent": [
+ "type",
+ "which",
+ "axis",
+ "padding1",
+ "padding2",
+ "value"
+ ],
+ "SDL_JoyButtonEvent": [
+ "type",
+ "which",
+ "button",
+ "state",
+ "padding1"
+ ],
"SDL_ResizeEvent": [
"type",
"w",
diff --git a/tests/sdl_joystick.c b/tests/sdl_joystick.c
new file mode 100644
index 00000000..7035050f
--- /dev/null
+++ b/tests/sdl_joystick.c
@@ -0,0 +1,123 @@
+#include <stdio.h>
+#include <SDL/SDL.h>
+#include <SDL/SDL_ttf.h>
+#include <assert.h>
+#include <string.h>
+#include <emscripten.h>
+
+int result = 1;
+
+void assertJoystickEvent(int expectedGamepad, int expectedType, int expectedIndex, int expectedValue) {
+ SDL_Event event;
+ while(1) {
+ // Loop ends either when assertion fails (we run out of events), or we find
+ // the event we're looking for.
+ assert(SDL_PollEvent(&event) == 1);
+ if (event.type != expectedType) {
+ continue;
+ }
+ switch(event.type) {
+ case SDL_JOYAXISMOTION: {
+ assert(event.jaxis.which == expectedGamepad);
+ assert(event.jaxis.axis == expectedIndex);
+ assert(event.jaxis.value == expectedValue);
+ break;
+ }
+ case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN: {
+ assert(event.jbutton.which == expectedGamepad);
+ assert(event.jbutton.button == expectedIndex);
+ assert(event.jbutton.state == expectedValue);
+ break;
+ }
+ }
+ // Break out of while loop.
+ break;
+ }
+}
+
+void assertNoJoystickEvent() {
+ SDL_Event event;
+ while(SDL_PollEvent(&event)) {
+ switch(event.type) {
+ case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: case SDL_JOYAXISMOTION: {
+ // Fail.
+ assert(0);
+ }
+ }
+ }
+}
+
+void main_2(void* arg);
+
+int main() {
+ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK);
+ SDL_Surface *screen = SDL_SetVideoMode(600, 450, 32, SDL_HWSURFACE);
+ emscripten_async_call(main_2, NULL, 3000); // avoid startup delays and intermittent errors
+ return 0;
+}
+
+void main_2(void* arg) {
+ // TODO: At the moment, we only support joystick support through polling.
+ emscripten_run_script("window.addNewGamepad('Pad Thai', 4, 16)");
+ emscripten_run_script("window.addNewGamepad('Pad Kee Mao', 0, 4)");
+ // Check that the joysticks exist properly.
+ assert(SDL_NumJoysticks() == 2);
+ assert(!SDL_JoystickOpened(0));
+ assert(!SDL_JoystickOpened(1));
+ SDL_Joystick* pad1 = SDL_JoystickOpen(0);
+ assert(SDL_JoystickOpened(0));
+ assert(SDL_JoystickIndex(pad1) == 0);
+ assert(strncmp(SDL_JoystickName(0), "Pad Thai", 9) == 0);
+ assert(strncmp(SDL_JoystickName(1), "Pad Kee Mao", 12) == 0);
+ assert(SDL_JoystickNumAxes(pad1) == 4);
+ assert(SDL_JoystickNumButtons(pad1) == 16);
+
+ // Button events.
+ emscripten_run_script("window.simulateGamepadButtonDown(0, 1)");
+ // We didn't tell SDL to automatically update this joystick's state.
+ assertNoJoystickEvent();
+ SDL_JoystickUpdate();
+ assertJoystickEvent(0, SDL_JOYBUTTONDOWN, 1, SDL_PRESSED);
+ assert(SDL_JoystickGetButton(pad1, 1) == 1);
+ // Enable automatic updates.
+ SDL_JoystickEventState(SDL_ENABLE);
+ assert(SDL_JoystickEventState(SDL_QUERY) == SDL_ENABLE);
+ emscripten_run_script("window.simulateGamepadButtonUp(0, 1)");
+ assertJoystickEvent(0, SDL_JOYBUTTONUP, 1, SDL_RELEASED);
+ assert(SDL_JoystickGetButton(pad1, 1) == 0);
+ // No button change: Should not result in a new event.
+ emscripten_run_script("window.simulateGamepadButtonUp(0, 1)");
+ assertNoJoystickEvent();
+ // Joystick 1 is not opened; should not result in a new event.
+ emscripten_run_script("window.simulateGamepadButtonDown(1, 1)");
+ assertNoJoystickEvent();
+
+ // Joystick wiggling
+ emscripten_run_script("window.simulateAxisMotion(0, 0, 1)");
+ assertJoystickEvent(0, SDL_JOYAXISMOTION, 0, 32767);
+ assert(SDL_JoystickGetAxis(pad1, 0) == 32767);
+ emscripten_run_script("window.simulateAxisMotion(0, 0, 0)");
+ assertJoystickEvent(0, SDL_JOYAXISMOTION, 0, 0);
+ assert(SDL_JoystickGetAxis(pad1, 0) == 0);
+ emscripten_run_script("window.simulateAxisMotion(0, 1, -1)");
+ assertJoystickEvent(0, SDL_JOYAXISMOTION, 1, -32768);
+ assert(SDL_JoystickGetAxis(pad1, 1) == -32768);
+ emscripten_run_script("window.simulateAxisMotion(0, 1, -1)");
+ // No joystick change: Should not result in a new event.
+ assertNoJoystickEvent();
+ // Joystick 1 is not opened; should not result in a new event.
+ emscripten_run_script("window.simulateAxisMotion(1, 1, -1)");
+ assertNoJoystickEvent();
+
+ SDL_JoystickClose(pad1);
+ assert(!SDL_JoystickOpened(0));
+
+ // Joystick 0 is closed; we should not process any new gamepad events from it.
+ emscripten_run_script("window.simulateGamepadButtonDown(0, 1)");
+ assertNoJoystickEvent();
+
+ // End test.
+ result = 2;
+ printf("Test passed!\n");
+}
+
diff --git a/tests/test_browser.py b/tests/test_browser.py
index d52f109f..1900e2cf 100644
--- a/tests/test_browser.py
+++ b/tests/test_browser.py
@@ -874,6 +874,82 @@ keydown(100);keyup(100); // trigger the end
def test_glut_wheelevents(self):
self.btest('glut_wheelevents.c', '1')
+ def test_sdl_joystick_1(self):
+ # Generates events corresponding to the Working Draft of the HTML5 Gamepad API.
+ # http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface
+ open(os.path.join(self.get_dir(), 'pre.js'), 'w').write('''
+ var gamepads = [];
+ // Spoof this function.
+ navigator['getGamepads'] = function() {
+ return gamepads;
+ };
+ window['addNewGamepad'] = function(id, numAxes, numButtons) {
+ var index = gamepads.length;
+ gamepads.push({
+ axes: new Array(numAxes),
+ buttons: new Array(numButtons),
+ id: id,
+ index: index
+ });
+ var i;
+ for (i = 0; i < numAxes; i++) gamepads[index].axes[i] = 0;
+ for (i = 0; i < numButtons; i++) gamepads[index].buttons[i] = 0;
+ };
+ window['simulateGamepadButtonDown'] = function (index, button) {
+ gamepads[index].buttons[button] = 1;
+ };
+ window['simulateGamepadButtonUp'] = function (index, button) {
+ gamepads[index].buttons[button] = 0;
+ };
+ window['simulateAxisMotion'] = function (index, axis, value) {
+ gamepads[index].axes[axis] = value;
+ };
+ ''')
+ open(os.path.join(self.get_dir(), 'sdl_joystick.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'sdl_joystick.c')).read()))
+
+ Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'sdl_joystick.c'), '-O2', '--minify', '0', '-o', 'page.html', '--pre-js', 'pre.js']).communicate()
+ self.run_browser('page.html', '', '/report_result?2')
+
+ def test_sdl_joystick_2(self):
+ # Generates events corresponding to the Editor's Draft of the HTML5 Gamepad API.
+ # https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-Gamepad
+ open(os.path.join(self.get_dir(), 'pre.js'), 'w').write('''
+ var gamepads = [];
+ // Spoof this function.
+ navigator['getGamepads'] = function() {
+ return gamepads;
+ };
+ window['addNewGamepad'] = function(id, numAxes, numButtons) {
+ var index = gamepads.length;
+ gamepads.push({
+ axes: new Array(numAxes),
+ buttons: new Array(numButtons),
+ id: id,
+ index: index
+ });
+ var i;
+ for (i = 0; i < numAxes; i++) gamepads[index].axes[i] = 0;
+ // Buttons are objects
+ for (i = 0; i < numButtons; i++) gamepads[index].buttons[i] = { pressed: false, value: 0 };
+ };
+ // FF mutates the original objects.
+ window['simulateGamepadButtonDown'] = function (index, button) {
+ gamepads[index].buttons[button].pressed = true;
+ gamepads[index].buttons[button].value = 1;
+ };
+ window['simulateGamepadButtonUp'] = function (index, button) {
+ gamepads[index].buttons[button].pressed = false;
+ gamepads[index].buttons[button].value = 0;
+ };
+ window['simulateAxisMotion'] = function (index, axis, value) {
+ gamepads[index].axes[axis] = value;
+ };
+ ''')
+ open(os.path.join(self.get_dir(), 'sdl_joystick.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'sdl_joystick.c')).read()))
+
+ Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'sdl_joystick.c'), '-O2', '--minify', '0', '-o', 'page.html', '--pre-js', 'pre.js']).communicate()
+ self.run_browser('page.html', '', '/report_result?2')
+
def test_webgl_context_attributes(self):
# Javascript code to check the attributes support we want to test in the WebGL implementation
# (request the attribute, create a context and check its value afterwards in the context attributes).