      return win.inputModes[mode];
    },
  
-@@ -1280,13 +1370,43 @@ var LibraryGLFW = {
+@@ -1280,13 +1379,49 @@ var LibraryGLFW = {
      GLFW.setScrollCallback(winid, cbfun);
    },
  
 -  glfwJoystickPresent: function(joy) { throw "glfwJoystickPresent is not implemented."; },
 +  glfwJoystickPresent: function(joy) {
-+    return GLFW.active.joys[joy] !== undefined;
++    GLFW.refreshJoysticks();
+ 
+-  glfwGetJoystickAxes: function(joy, count) { throw "glfwGetJoystickAxes is not implemented."; },
++    return GLFW.joys[joy] !== undefined;
 +  },
 +
 +  glfwGetJoystickAxes: function(joy, count) {
-+    GLFW.refreshJoystick(joy);
-+
-+    var state = GLFW.active.joys[joy];
++    GLFW.refreshJoysticks();
+ 
+-  glfwGetJoystickButtons: function(joy, count) { throw "glfwGetJoystickButtons is not implemented."; },
++    var state = GLFW.joys[joy];
 +    if (!state || !state.axes) {
 +      setValue(count, 0, 'i32');
 +      return;
 +    }
-+
+ 
+-  glfwGetJoystickName: function(joy) { throw "glfwGetJoystickName is not implemented."; },
 +    setValue(count, state.axesCount, 'i32');
 +    return state.axes;
 +  },
- 
--  glfwGetJoystickAxes: function(joy, count) { throw "glfwGetJoystickAxes is not implemented."; },
++
 +  glfwGetJoystickButtons: function(joy, count) {
-+    GLFW.refreshJoystick(joy);
- 
--  glfwGetJoystickButtons: function(joy, count) { throw "glfwGetJoystickButtons is not implemented."; },
-+    var state = GLFW.active.joys[joy];
++    GLFW.refreshJoysticks();
++
++    var state = GLFW.joys[joy];
 +    if (!state || !state.buttons) {
 +      setValue(count, 0, 'i32');
 +      return;
 +    }
- 
--  glfwGetJoystickName: function(joy) { throw "glfwGetJoystickName is not implemented."; },
++
 +    setValue(count, state.buttonsCount, 'i32');
 +    return state.buttons;
 +  },
 +
 +  glfwGetJoystickName: function(joy) {
-+    return GLFW.active.joys[joy].id;
++    if (GLFW.joys[joy]) {
++      return GLFW.joys[joy].id;
++    } else {
++      return 0;
++    }
 +  },
 +
 +  glfwSetJoystickCallback: function(cbfun) {
-+    GLFW.setJoystickCallback(GLFW.active.id, cbfun);
++    GLFW.setJoystickCallback(cbfun);
 +  },
  
    glfwSetClipboardString: function(win, string) {},

       if (!GLFW.active.keyFunc) return;
 
 #if USE_GLFW == 2
@@ -375,12 +381,44 @@ var LibraryGLFW = {
 
 #if USE_GLFW == 3
       if (repeat) status = 2; // GLFW_REPEAT

     },
 
     onMousemove: function(event) {
@@ -628,6 +649,67 @@ var LibraryGLFW = {
       }
     },
 
+    setJoystickCallback: function(cbfun) {
+      GLFW.joystickFunc = cbfun;
+    },
+
+    joys: {}, // glfw joystick data
+    lastGamepadState: null,
+    lastGamepadStateFrame: null, // The integer value of Browser.mainLoop.currentFrameNumber of when the last gamepad state was produced.
+
+    refreshJoysticks: function() {
+      // Produce a new Gamepad API sample if we are ticking a new game frame, or if not using emscripten_set_main_loop() at all to drive animation.
+      if (Browser.mainLoop.currentFrameNumber !== GLFW.lastGamepadStateFrame || !Browser.mainLoop.currentFrameNumber) {
+        GLFW.lastGamepadState = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : null);
+        GLFW.lastGamepadStateFrame = Browser.mainLoop.currentFrameNumber;
+
+        for (var joy = 0; joy < GLFW.lastGamepadState.length; ++joy) {
+          var gamepad = GLFW.lastGamepadState[joy];
+
+          if (gamepad) {
+            if (!GLFW.joys[joy]) {
+              console.log('glfw joystick connected:',joy);
+              GLFW.joys[joy] = {
+                id: allocate(intArrayFromString(gamepad.id), 'i8', ALLOC_NORMAL),
+                buttonsCount: gamepad.buttons.length,
+                axesCount: gamepad.axes.length,
+                buttons: allocate(new Array(gamepad.buttons.length), 'i8', ALLOC_NORMAL),
+                axes: allocate(new Array(gamepad.axes.length*4), 'float', ALLOC_NORMAL)
+              };
+
+              if (GLFW.joystickFunc) {
+                Module['dynCall_vii'](GLFW.joystickFunc, joy, 0x00040001); // GLFW_CONNECTED
+              }
+            }
+
+            var data = GLFW.joys[joy];
+
+            for (var i = 0; i < gamepad.buttons.length;  ++i) {
+              setValue(data.buttons + i, gamepad.buttons[i].pressed, 'i8');
+            }
+
+            for (var i = 0; i < gamepad.axes.length; ++i) {
+              setValue(data.axes + i*4, gamepad.axes[i], 'float');
+            }
+          } else {
+            if (GLFW.joys[joy]) {
+              console.log('glfw joystick disconnected',joy);
+
+              if (GLFW.joystickFunc) {
+                Module['dynCall_vii'](GLFW.joystickFunc, joy, 0x00040002); // GLFW_DISCONNECTED
+              }
+
+              _free(GLFW.joys[joy].id);
+              _free(GLFW.joys[joy].buttons);
+              _free(GLFW.joys[joy].axes);
+
+              delete GLFW.joys[joy];
+            }
+          }
+        }
+      }
+    },
+
     setKeyCallback: function(winid, cbfun) {
       var win = GLFW.WindowFromId(winid);
       if (!win) return;
@@ -941,9 +1023,12 @@ var LibraryGLFW = {
     GLFW.windows = new Array()
     GLFW.active = null;
 
