5

I would like to write an application where the keybindings are specific to the location of the key on the keyboard, not the character they are mapped to. For example, the key that is between t and u on a US keyboard should perform a specific function, regardless of whether it is Y as it is in the US or Z as it is in Germany.

I think the way to do this would be to get the actual scan code given by the keyboard to the OS to represent the key that was pressed. How can I do this in java?

Or is there another way to achieve the same functionality?

Scaatis
  • 137
  • 2
  • 10

2 Answers2

5

Excerpt from Oracle's KeyEvent source code:

//set from native code.
private transient long rawCode = 0;
private transient long primaryLevelUnicode = 0;
private transient long scancode = 0; // for MS Windows only
private transient long extendedKeyCode = 0;

First, I thought about parsing the KeyEvent's toString() return value because it contains the scancode. But then I wrote a utility method (Tried successfully on Windows 8.) that uses reflection:

final public static Integer getScancodeFromKeyEvent(final KeyEvent keyEvent) {

    Integer ret;
    Field field;

    try {
        field = KeyEvent.class.getDeclaredField("scancode");
    } catch (NoSuchFieldException nsfe) {
        System.err.println("ATTENTION! The KeyEvent object does not have a field named \"scancode\"! (Which is kinda weird.)");
        nsfe.printStackTrace();
        return null;
    }

    try {
        field.setAccessible(true);
    } catch (SecurityException se) {
        System.err.println("ATTENTION! Changing the accessibility of the KeyEvent class' field \"scancode\" caused a security exception!");
        se.printStackTrace();
        return null;
    }

    try {
        ret = (int) field.getLong(keyEvent);
    } catch (IllegalAccessException iae) {
        System.err.println("ATTENTION! It is not allowed to read the field \"scancode\" of the KeyEvent instance!");
        iae.printStackTrace();
        return null;
    }

    return ret;
}

Apparently, resetting the field's accessibility afterwards is not necessary, as I got an exception when using this method while the setAccessible() line was commented out. (I changed it on the fly back and forth and recompiled in IntelliJ while the runtime was still on. Should be the same in Eclipse.) Would be easily possible, though, using the isAccessible() method first.

I needed the scancode for playing music on the keyboard because changing the keyboard language between QWERTZ and QWERTY swapped the notes accordeonly. It's really sad that we don't have legal access to a scancode-like value. The above solution successfully ignored the current keyboard layout configuration.

By the way, toString return value for "z" and "y" with US keyboard layout:

[KEY_PRESSED,keyCode=90,keyText=Z,keyChar='z',keyLocation=KEY_LOCATION_STANDARD,rawCode=90,primaryLevelUnicode=122,scancode=44,extendedKeyCode=0x5a] on frame0]
[KEY_PRESSED,keyCode=89,keyText=Y,keyChar='y',keyLocation=KEY_LOCATION_STANDARD,rawCode=89,primaryLevelUnicode=121,scancode=21,extendedKeyCode=0x59] on frame0]

With DE keyboard layout:

[KEY_PRESSED,keyCode=89,keyText=Y,keyChar='y',keyLocation=KEY_LOCATION_STANDARD,rawCode=89,primaryLevelUnicode=121,scancode=44,extendedKeyCode=0x59] on frame0]
[KEY_PRESSED,keyCode=90,keyText=Z,keyChar='z',keyLocation=KEY_LOCATION_STANDARD,rawCode=90,primaryLevelUnicode=122,scancode=21,extendedKeyCode=0x5a] on frame0]

Mind the scancode.

As a bonus, off-topic:

[KEY_PRESSED,keyCode=10,keyText=Enter,keyChar=Enter,keyLocation=KEY_LOCATION_STANDARD,rawCode=13,primaryLevelUnicode=13,scancode=28,extendedKeyCode=0xa] on frame0]
[KEY_PRESSED,keyCode=10,keyText=Enter,keyChar=Enter,keyLocation=KEY_LOCATION_NUMPAD,rawCode=13,primaryLevelUnicode=13,scancode=28,extendedKeyCode=0xa] on frame0]
[KEY_PRESSED,keyCode=49,keyText=1,keyChar='1',keyLocation=KEY_LOCATION_STANDARD,rawCode=49,primaryLevelUnicode=49,scancode=2,extendedKeyCode=0x31] on frame0]
[KEY_PRESSED,keyCode=97,keyText=NumPad-1,keyChar='1',keyLocation=KEY_LOCATION_NUMPAD,rawCode=97,primaryLevelUnicode=49,scancode=79,extendedKeyCode=0x61] on frame0]
Dreamspace President
  • 1,060
  • 13
  • 33
  • 1
    Thanks this looks usefull. To sad java is designed in such a shit fashion. – clankill3r Apr 16 '19 at 16:14
  • 1
    Does someone know what the `rawCode` is for? – clankill3r Apr 17 '19 at 17:50
  • For Oracle implementation on Linux, scancode is always 0 for all keys :( – Hatoru Hansou Oct 11 '19 at 05:52
  • @HatoruHansou Yes, unfortunately the scancode field only has a value on Windows (which is frustrating to say the least). So they just commented "// for MS Windows only" behind it. :/ – Dreamspace President Oct 12 '19 at 16:15
  • @DreamspacePresident Very frustrating. It has a lot of pretty valid use cases. I did a lot of tests like launching with sudo and trying different java installations and whatnot. Linux native libraries seems the way to go for Linux. – Hatoru Hansou Oct 12 '19 at 16:47
  • @HatoruHansou "Linux native libraries seems the way to go for Linux." Are you saying that there are Linux native Java libraries, and when you use those, the `scancode` field is not 0? – Dreamspace President Oct 14 '19 at 07:19
  • 1
    @DreamspacePresident, No, after all the test I did, I'm pretty sure KeyEvent.scancode will always be 0 on Linux. But you can use JNI to grab physical key codes the Linux way. I cannot tell you now if Linux call them scancodes, but I'm sure there is a way using standard C libraries. For example, SDL can report physical key codes, and... actually, load SDL using JNI is a valid option, maybe an overkill one. You don't need to import all the functions, only input handling ones. – Hatoru Hansou Oct 14 '19 at 16:26
4

As MadProgrammer said: You have to use JNA or JNI. You can also have a look at those projects:

JIntellitype is a Java API for interacting with Microsoft Intellitype commands as well as registering for Global Hotkeys in your Java application. The API is a Java JNI library that uses a C++ DLL to do all the communication with Windows.

There are similar projects Linux and Mac OS X.


JNativeHook is a library to provide global keyboard and mouse listeners for Java. This will allow you to listen for global shortcuts or mouse motion that would otherwise be impossible using pure Java. To accomplish this task, JNativeHook leverages platform dependent native code through Java's native interface to create low level system wide hooks and deliver those events to your application.


Windows only, capable of Win 7 / 8 (32 and 64 bit)


Community
  • 1
  • 1
ollo
  • 24,797
  • 14
  • 106
  • 155