jmonkeyengine: Keyboard Mapping inconsistency between lwjgl2 and 3

This issue might need further testing because I think this was different on Windows than on the linux I am currently seeing this bug. It probably also only affects non-english people but certainly those with a german (QWERTZ) layout. The problem is that lwjgl3 doesn’t seem to care about to local keyboard layout and treat it as if it was an us-layout causing confusion.

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;

/**
 * Small Testcase for (at least) german keyboards: Behavior on "y" is different from lwjgl2 to lwjgl3 (3 doesn't seem
 * to respect the local keyboard mapping)
 * @author normenhansen
 */
public class Main extends SimpleApplication implements ActionListener {

    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        inputManager.addMapping("y", new KeyTrigger(KeyInput.KEY_Y));
        inputManager.addMapping("z", new KeyTrigger(KeyInput.KEY_Z));
        inputManager.addListener(this, "y", "z");
    }

    @Override
    public void onAction(String name, boolean isPressed, float tpf) {
        System.out.println(name + ": " + isPressed);
    }
}

Run this once with lwjgl2 and once with lwjgl3 and in 3 when you press y you actually see “z” printed and vice-versa.

For convenience I’ve added a slightly modified version of Stephens “BasicGame-on-Gradle” so that it works with the commandline, you just need to comment out lwjgl3 and execute ./gradlew run: (Gradle doesn’t automatically add run otherwise and the mainclass is also a different property)

apply plugin: 'java'
apply plugin: 'application'

// select one source-code option
//sourceCompatibility = '1.6'
//sourceCompatibility = '1.7'
sourceCompatibility = '1.8'

gradle.projectsEvaluated {
    tasks.withType(JavaCompile) {
        options.compilerArgs << '-Xdiags:verbose'
        options.compilerArgs << '-Xlint:unchecked'
        options.deprecation = true
        options.encoding = 'UTF-8'
    }
    tasks.withType(JavaExec) {
        args = []
        classpath sourceSets.main.runtimeClasspath
        //debug true
        enableAssertions true
        jvmArgs '-XX:+UseConcMarkSweepGC'
        //jvmArgs '-XX:+UseG1GC', '-XX:MaxGCPauseMillis=10'
    }
}

// select one version of the Engine
//ext.jmonkeyengineVersion = '3.1.0-stable' // from jcenter
//ext.jmonkeyengineVersion = '3.2.0-stable' // from jcenter
ext.jmonkeyengineVersion = '3.2.1-stable' // from jcenter
//ext.jmonkeyengineVersion = '3.2.1-SNAPSHOT' // from mavenLocal

// NetBeans will automatically add "run" and "debug" tasks relying on the
// "mainClass" property. You may however define the property prior executing
// tasks by passing a "-PmainClass=<QUALIFIED_CLASS_NAME>" argument.
//
// Note however, that you may define your own "run" and "debug" task if you
// prefer. In this case NetBeans will not add these tasks but you may rely on
// your own implementation.
if (!hasProperty('mainClass')) {
    mainClassName = 'mygame.Main'
}

repositories {
    //mavenLocal()
    jcenter()
    //maven { url 'http://nifty-gui.sourceforge.net/nifty-maven-repo' }
    //maven { url 'https://dl.bintray.com/stephengold/org.jmonkeyengine' }
    //google()
    //mavenCentral()
    // Read more about repositories here:
    //   https://docs.gradle.org/current/userguide/dependency_management.html#sec:repositories
}

dependencies {
    // You can read more about how to add dependencies here:
    //   https://docs.gradle.org/current/userguide/dependency_management.html#sec:how_to_declare_your_dependencies

    // from jcenter (or mavenLocal) repositories:
    compile "org.jmonkeyengine:jme3-core:$jmonkeyengineVersion"
    runtime "org.jmonkeyengine:jme3-blender:$jmonkeyengineVersion"
    //compile "org.jmonkeyengine:jme3-bullet:$jmonkeyengineVersion"
    //runtime "org.jmonkeyengine:jme3-bullet-native:$jmonkeyengineVersion"
    runtime "org.jmonkeyengine:jme3-desktop:$jmonkeyengineVersion"
    //compile "org.jmonkeyengine:jme3-effects:$jmonkeyengineVersion"
    //runtime "org.jmonkeyengine:jme3-jogg:$jmonkeyengineVersion"
    //compile "org.jmonkeyengine:jme3-jogl:$jmonkeyengineVersion"
    //compile "org.jmonkeyengine:jme3-networking:$jmonkeyengineVersion"
    //compile "org.jmonkeyengine:jme3-niftygui:$jmonkeyengineVersion"
    runtime "org.jmonkeyengine:jme3-plugins:$jmonkeyengineVersion"
    //compile "org.jmonkeyengine:jme3-terrain:$jmonkeyengineVersion"

    // select one version of LWJGL (from jcenter or mavenLocal)
    //runtime "org.jmonkeyengine:jme3-lwjgl:$jmonkeyengineVersion"
    runtime "org.jmonkeyengine:jme3-lwjgl3:$jmonkeyengineVersion"

    // from stephengold's bintray repositories:
    //runtime 'org.jmonkeyengine:jme3-testdata:3.1.0-stable'
}

// cleanup tasks
clean { dependsOn 'cleanDLLs', 'cleanSOs' }
task cleanDLLs(type: Delete) {
    delete fileTree(dir: '.', include: '*.dll')
}
task cleanSOs(type: Delete) {
    delete fileTree(dir: '.', include: '*.so')
}

I am not sure if this issue isn’t within lwjgl3 itself though or rather our “configuration”. Maybe it’s also already fixed upstream and we just need a newer version

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 25 (25 by maintainers)

Most upvoted comments

I can go ahead and commit https://github.com/jMonkeyEngine/jmonkeyengine/commit/b024cf635d569ebe417e8c8e97e702bd87227388 to the master if everybody is satisfied with this solution. Is it a problem to have default methods in interfaces? I’m thinking about android and iOS deployments.

Well i guess it depends on the game genre I can implement the glfwGetKeyName api, and expose a method ( inputManager.getKeyName(int) )to get the layout specific name for jme keycodes. This solves the documentation issue and can partially solve the mnemonic issue too, since it is possible to just cycle through all the jme keycodes and find the one that matches the mnemonic key name as a form of auto remapping. Eg. I want to map L to launch

int launchKey=KeyInput.KEY_L; // L position in US layout
forEach(int key in jmeKeys){
  if(inputManager.getKeyName(key).equals("L")){
     //remap
     launchKey=key; // layout specific position of L
     break;
  }
}
System.out.println("Use "+inputManager.getKeyName(launchKey)+" to launch");

if the key name is not found (eg. on russian keyboards) it won’t remap and the positional key will be used. With this code it is also possible to use mnemonic keys for languages that don’t use latin letters.

Documenting keyboard controls based on scan codes would be inconvenient. It’s simpler and clearer to say “press the Y key” than to say “press the key that generates scan code 21 decimal”.

You are going to need to remap all of them anyway, since they will be in wrong positions. Imagine telling a french player to use WASD to move. KA-French-20823 When you remap the keys you can save their charcode.

A lot of games need the actual chars. Where’s the API for doing the proper remapping?

The test case here is wrong. The code to read input characters should be:

package com.mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;

/**
 * Small Testcase for (at least) german keyboards: Behavior on "y" is different from lwjgl2 to lwjgl3 (3 doesn't seem
 * to respect the local keyboard mapping)
 * @author normenhansen
 */
public class Test extends SimpleApplication implements RawInputListener{

    public static void main(String[] args) {
        Test app=new Test();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        inputManager.addRawInputListener(this);
    }

    @Override
    public void beginInput() {
    }

    @Override
    public void endInput() {
    }

    @Override
    public void onJoyAxisEvent(JoyAxisEvent evt) {
    }

    @Override
    public void onJoyButtonEvent(JoyButtonEvent evt) {
    }

    @Override
    public void onMouseMotionEvent(MouseMotionEvent evt) {
    }

    @Override
    public void onMouseButtonEvent(MouseButtonEvent evt) {
    }

    @Override
    public void onKeyEvent(KeyInputEvent evt) {
        if(evt.getKeyChar()!=0){
            System.out.println("Input char: "+evt.getKeyChar());
        }else{
            System.out.println("Input code: "+evt.getKeyCode());
        }
    }

    @Override
    public void onTouchEvent(TouchEvent evt) {
    }
}

I tried it and it seems to read the proper characters