[Solved] Gamepad disconnect/re-connect

Is there any way on jME to detect when a gamepad is disconnected so we can take an action like pausing the game or something?

As an exercise I tried the following simple code using both LWJGL 2 and 3, starting with a gamepad connected to my computer and suddenly disconnecting it. The behavior was different with each version of LWJGL. On version 2 the logger repeatedly shows an info that won’t stop when the controller is re-connected. On version 3, it fails miserably with a NullPointerException.

What should I do? Is there some sort of listener or something that I can use? I don’t think I should catch NullPointerException because it could be anything, not only gamepad disconnection. Besides it would work only on LWJGL 3.

If there is nothing available, don’t you guys think we should create a JoystickDisconnectedException or something in jME? Or maybe something like a listener to detect disconnection/re-connection of gamepads?

Code:

import com.jme3.app.SimpleApplication;
import com.jme3.system.AppSettings;

public class Main extends SimpleApplication {

	public static void main(String... args) {
		AppSettings settings = new AppSettings(true);
		settings.setUseJoysticks(true);

		Main app = new Main();
		app.setSettings(settings);
		app.start();
	}

	@Override
	public void simpleInitApp() {
	}
}

Result when joystick is disconnected (LWJGL 2):

May 09, 2019 4:41:03 PM net.java.games.input.ControllerEnvironment log
INFO: Failed to poll device: Failed to get device key states (19)

May 09, 2019 4:41:03 PM net.java.games.input.ControllerEnvironment log
INFO: Failed to poll device: Failed to get device key states (19)

May 09, 2019 4:41:03 PM net.java.games.input.ControllerEnvironment log
INFO: Failed to poll device: Failed to get device key states (19)

Result when joystick is disconnected (LWJGL 3):

May 09, 2019 4:42:46 PM com.jme3.app.LegacyApplication handleError
SEVERE: Uncaught exception thrown in Thread[main,5,main]
java.lang.NullPointerException
	at com.jme3.input.lwjgl.GlfwJoystickInput.update(GlfwJoystickInput.java:133)
	at com.jme3.input.InputManager.update(InputManager.java:906)
	at com.jme3.app.LegacyApplication.update(LegacyApplication.java:724)
	at com.jme3.app.SimpleApplication.update(SimpleApplication.java:244)
	at com.jme3.system.lwjgl.LwjglWindow.runLoop(LwjglWindow.java:499)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:581)
	at com.jme3.system.lwjgl.LwjglWindow.create(LwjglWindow.java:423)
	at com.jme3.app.LegacyApplication.start(LegacyApplication.java:463)
	at com.jme3.app.LegacyApplication.start(LegacyApplication.java:424)
	at com.jme3.app.SimpleApplication.start(SimpleApplication.java:125)
	at Main.main(Main.java:14)

As far as I know, JInput doesn’t give us any indication either.

When I looked into it before, the only way to detect this was to constantly reinitialize the input library and reload the joysticks. Not pretty.

Maybe adding this check can fix the issue (at least the null pointer exception) for LWJGL3. Not sure if it will recognise it after re-connect so.

if (!glfwJoystickPresent(entry.getKey())) {
    continue;
}

after this line

Searching google, it seems there is not a controller listener for connect/disconnect in LWJGL3/GLFW.

It seems I did not search it properly. It seems there is a callback for this:

https://www.glfw.org/docs/latest/input_guide.html#joystick_event

1 Like

I submitted an issue on GitHub page

I do not have a joystick, unfortunately, I can not test it on my side.

1 Like

@Ali_RS Well done!

I think we should also create a listener on InputManager (or somewhere else, wherever more suitable), then we could do something like:

inputManager.addJoystickConnectionListener(myListener);

This way myListener would be called everytime a joystick is connected/disconnected, letting us do something about it, like pausing the game.

…corrected that slightly.

Sure, thank you!

The next step would be to open an issue at GitHub:
https://github.com/jMonkeyEngine/jmonkeyengine/issues/new

Like the one already opened?

Or a different one?

1 Like

That one’s good. I must’ve missed it somehow.

Not sure where we should do this, InputManager can not directly use GLFWJoystickCallback.java as it is LWJGL3 specific.

Maybe should be a different one. :slightly_smiling_face:
The issue I have submitted is only intended for the NPE.

The OP is using lwjgl and not lwjgl3 so the stacktrace is off. What’s the actual reproduction procedure?

Plug in a joystick - start the game - unplug? Do I need to bind anything to the joystick? Can I plug a joystick in after the game started?

OP tried both and the stack trace is from lwjgl 3. lwjgl 2 just logs endless error messages.

Yep, and this should result in an NPE on LWJGL3.

Not sure about this.

AFAIK, this should not have any effect, as InputManager only loads joysticks once upon game start.

My guess is that after apply this patch on LWJGL3:

we should be able to disconnect joystick while game is running and reconnect it again and it will work.

blimey. Ok i’ll take this on. I’m getting a response from the callback I made, but that’s just step one. This may take a bit of tinkering…

1 Like

Well the good news is:

  • You can add joysticks AFTER the game is loaded.
  • You don’t need to plug the joystick in BEFORE the game starts.
  • You are notified if a joystick is added or removed.
  • No errors are thrown if a joystick is removed.

The bad news is that I only want to implement this in LWJGL3 - which requires a cast for the callback.

((GlfwJoystickInput)getContext().getJoyInput()).setJoystickCallback(new GLFWJoystickCallback() {
            @Override
            public void invoke(int jid, int event) {
                System.out.println(event == GLFW_CONNECTED
                        ? "Joystick Connected:" + jid
                        : "Joystick Disconnected:" + jid
            );

            }
        });

This is pretty ugly, but aside from this everything works. I’m not sure how I can get around this because many contexts use the same interface, and the only context I want is the LWJGL3 one :confused:

Ok. I think I’m done here. I’ve moved the callback to the inputManager. The event is only fired from the LWJGL3 context, so adding a listener using any other context just wont fire any event to the listeners. It will be up to the other contexts to fire the event if/when they get support.

getInputManager().addJoystickConnectionListener(new JoystickConnectionListener(){

            @Override
            public void connectionChanged(int joystickId, JoystickState action) {
                System.out.println(action == JoystickState.CONNECTED
                        ? "Joystick Connected:" + joystickId
                        : "Joystick Disconnected:" + joystickId
                );
            }
        });

I’ll leave it for an hour or two to go around my head a few times and push a PR for it.

1 Like