(Edited for clarity)
I want to detect when a user presses and releases a key in Java Swing, ignoring the keyboard auto repeat feature. I also would like a pure Java approach the works on Linux, Mac OS and Windows.
Requirements:
- When the user presses some key I want to know what key is that;
- When the user releases some key, I want to know what key is that;
- I want to ignore the system auto repeat options: I want to receive just one keypress event for each key press and just one key release event for each key release;
- If possible, I would use items 1 to 3 to know if the user is holding more than one key at a time (i.e, she hits 'a' and without releasing it, she hits "Enter").
The problem I'm facing in Java is that under Linux, when the user holds some key, there are many keyPress and keyRelease events being fired (because of the keyboard repeat feature).
I've tried some approaches with no success:
- Get the last time a key event occurred - in Linux, they seem to be zero for key repeat, however, in Mac OS they are not;
- Consider an event only if the current keyCode is different from the last one - this way the user can't hit twice the same key in a row;
Here is the basic (non working) part of code:
import java.awt.event.KeyListener;
public class Example implements KeyListener {
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
System.out.println("KeyPressed: "+e.getKeyCode()+", ts="+e.getWhen());
}
public void keyReleased(KeyEvent e) {
System.out.println("KeyReleased: "+e.getKeyCode()+", ts="+e.getWhen());
}
}
When a user holds a key (i.e, 'p') the system shows:
KeyPressed: 80, ts=1253637271673
KeyReleased: 80, ts=1253637271923
KeyPressed: 80, ts=1253637271923
KeyReleased: 80, ts=1253637271956
KeyPressed: 80, ts=1253637271956
KeyReleased: 80, ts=1253637271990
KeyPressed: 80, ts=1253637271990
KeyReleased: 80, ts=1253637272023
KeyPressed: 80, ts=1253637272023
...
At least under Linux, the JVM keeps resending all the key events when a key is being hold. To make things more difficult, on my system (Kubuntu 9.04 Core 2 Duo) the timestamps keep changing. The JVM sends a key new release and new key press with the same timestamp. This makes it hard to know when a key is really released.
Any ideas?
Thanks
This approach stores key presses in a HashMap, resetting them when the key is released. Most of the code is courtesy of Elist in this post.
You can use the HashMap to check if a certain key is pressed, or call
KeyboardInput2.allPressed()
to print every pressed key.Save the timestamp of the event (
arg0.when()
) inkeyReleased
. If the nextkeyPressed
event is for the same key and has the same timestamp, it is an autorepeat.If you hold down multiple keys, X11 only autorepeats the last key pressed. So, if you hold down 'a' and 'd' you'll see something like:
You might want to use the action map of the component you are interested in. Here's an example that deals with a specific key (SPACE BAR) but I'm sure that if you read the documentation you may be able to modify it to handle generic key presses and releases.
I've found a solution that does without waiting in case you have something like a game loop going. The idea is storing the release events. Then you can check against them both inside the game loop and inside the key pressed handler. By "(un)register a key" I mean the extracted true press/release events that should be processed by the application. Take care of synchronization when doing the following!
I've refined stolsvik hack to prevent repeating of KEY_PRESSED and KEY_TYPED events as well, with this refinement it works correctly under Win7 (should work everywhere as it truly watches out for KEY_PRESSED/KEY_TYPED/KEY_RELEASED events).
Cheers! Jakub
What am I not getting about all the elaborate but questionable suggestions? The solution is so simple!(Overlooked the key part of OP's question: "under Linux, when the user holds some key, there are many keyPress and keyRelease events being fired")In your keyPress event, check if the keyCode is already in a Set<Integer>. If it is, it must be an autorepeat event. If it is not, put it in and digest it. In your keyRelease event, blindly remove the keyCode from the Set - assuming that OP's statement about many keyRelease events is false. On Windows, I only get several keyPresses, but only one keyRelease.
To abstract this a little, you could create a wrapper that can carry KeyEvents, MouseEvents, and MouseWheelEvents and has a flag that already says that the keyPress is just an autorepeat.