Search code examples
javacontrollerreloadjinput

Java Jinput: rescan / reload controllers


I am using java jinput library to read data from joypad, and I have trouble reloading Controllers, I use this to load them:

public Controller[] findStickControllers() {
    ControllerEnvironment ce =
            ControllerEnvironment.getDefaultEnvironment();

    Controller[] cs = ce.getControllers();

    System.out.println(cs.length); //test

    ArrayList<Controller> sel = new ArrayList<>();

    for (Controller c: cs) {
        if(c.getType() == Type.STICK) {
            sel.add(c);
        }
    }

    return sel.toArray(new Controller[]{});
}

This works fine, but if I disconnect my controller, calling this will find it again, and vice versa (connecting it after the first check will not find it at all).

I have tried to put sleep before the fist lookup, with these results:

  1. Controllers are acctually scanned when this method is called first time (not at start of the program)
  2. When called again, this always returns same controllers as it returned for the first time.
  3. First call will also write warning bellow
  4. Even when controller is connected (and works), then disconnected (it will still find it though) and reconnected, it will not work

Warning from point 3: (didn't format well in the list)

WARNING: Found unknown Windows version: Windows 8
Attempting to use default windows plug-in.
Loading: net.java.games.input.DirectAndRawInputEnvironmentPlugin

I am using Win 8, and had same problem on Win 7. I had also tried this with mouse, same results.

How can I acctually reload controllers for the 2nd, 3rd, and so on time?


Solution

  • I encountered the same problem. The reason is that the actual hardware scan happens only once for each DefaultControllerEnvironment object. Since the only accessible instantiation is a singleton, it never does another scan.

    A simple way to force a hardware scan is to create a new object, but neither the class nor the constructor are public. You can however work around this limitation by calling the constructor via reflection.

    Rescan

    private static ControllerEnvironment createDefaultEnvironment() throws ReflectiveOperationException {
    
        // Find constructor (class is package private, so we can't access it directly)
        Constructor<ControllerEnvironment> constructor = (Constructor<ControllerEnvironment>)
            Class.forName("net.java.games.input.DefaultControllerEnvironment").getDeclaredConstructors()[0];
    
        // Constructor is package private, so we have to deactivate access control checks
        constructor.setAccessible(true);
    
        // Create object with default constructor
        return constructor.newInstance();
    }
    

    Usage

    // Be aware that creating a new environment is fairly expensive
    Controller[] controllers = createDefaultEnvironment().getControllers();
    

    Remove Windows 8 Warnings

    /**
     * Fix windows 8 warnings by defining a working plugin
     */
    static {
    
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                String os = System.getProperty("os.name", "").trim();
                if (os.startsWith("Windows 8")) {  // 8, 8.1 etc.
    
                    // disable default plugin lookup
                    System.setProperty("jinput.useDefaultPlugin", "false");
    
                    // set to same as windows 7 (tested for windows 8 and 8.1)
                    System.setProperty("net.java.games.input.plugins", "net.java.games.input.DirectAndRawInputEnvironmentPlugin");
    
                }
                return null;
            }
        });
    
    }