views:

783

answers:

5

(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:

  1. When the user presses some key I want to know what key is that;
  2. When the user releases some key, I want to know what key is that;
  3. 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;
  4. 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:

  1. 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;
  2. 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

A: 

Well, you said that it is possible that the time between key events in case of key repeat be non-negative. Even so, it is likely very short. You could then threshold this time to some very small value, and everything equal or lower than it be considered a key repeat.

luvieere
I didn't want to rely on specific timings to do this, because of portability. Anyway, I will test again to see if it will be possible to isolate a real keyRelease event from the fake ones by measuring the time between events.
Luis Soeiro
I've done more tests and updated the question.
Luis Soeiro
+2  A: 

This could be problematic. I can't remember for sure (it's been a long time), but it's likely the repeating-key feature (which is handled by the underlying operating system, not Java) isn't providing enough information for the JVM developer to distinguish those additional key events from the 'real' one. (I worked on this in the OS/2 AWT back in 1.1.x by the way).

From the javadoc for KeyEvent:

"Key pressed" and "key released" events are lower-level and depend on the platform and keyboard layout. They are generated whenever a key is pressed or released, and are the only way to find out about keys that don't generate character input (e.g., action keys, modifier keys, etc.). The key being pressed or released is indicated by the getKeyCode method, which returns a virtual key code.

As I recall from doing this in OS/2 (which at the time still had only the 2-event up/down flavor of keyboard handling like older versions of Windows, not the 3-event up/down/char flavor you get in more modern versions), I didn't report KeyReleased events any differently if the key was just being held down and the events auto-generated; but I suspect OS/2 didn't even report that information to me (can't remember for sure). We used the Windows reference JVM from Sun as our guide for developing our AWT - so I suspect if it were possible to report this information there, I'd have at least seen it on their end.

M1EK
A: 

Save the timestamp of the event (arg0.when()) in keyReleased. If the next keyPressed 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:

a down
a up
a down
d down
d up
d down
d up
a up
Devon_C_Miller
A: 

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.

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;

import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

public class Main {
    public static void main(String[] args) {
     JFrame f = new JFrame("Test");
     JPanel c = new JPanel();

     c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
       KeyStroke.getKeyStroke("SPACE"), "pressed");
     c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
       KeyStroke.getKeyStroke("released SPACE"), "released");
     c.getActionMap().put("pressed", new Action() {
      public void addPropertyChangeListener(
        PropertyChangeListener listener) {
      }

      public Object getValue(String key) {
       return null;
      }

      public boolean isEnabled() {
       return true;
      }

      public void putValue(String key, Object value) {
      }

      public void removePropertyChangeListener(
        PropertyChangeListener listener) {
      }

      public void setEnabled(boolean b) {
      }

      public void actionPerformed(ActionEvent e) {
       System.out.println("Pressed space at "+System.nanoTime());
      }
     });
     c.getActionMap().put("released", new Action() {
      public void addPropertyChangeListener(
        PropertyChangeListener listener) {
      }

      public Object getValue(String key) {
       return null;
      }

      public boolean isEnabled() {
       return true;
      }

      public void putValue(String key, Object value) {
      }

      public void removePropertyChangeListener(
        PropertyChangeListener listener) {
      }

      public void setEnabled(boolean b) {
      }

      public void actionPerformed(ActionEvent e) {
       System.out.println("Released space at "+System.nanoTime());
      }
     });
     c.setPreferredSize(new Dimension(200,200));


     f.getContentPane().add(c);
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     f.pack();
     f.setVisible(true);
    }
}
Savvas Dalkitsis
Thanks for the attempt. But here I get the same results: lots of repetitions, but this time different time stamps for each code.Released space at 7623492422327Pressed space at 7623492546365Released space at 7623525621728Pressed space at 7623525754217Released space at 7623559107407Pressed space at 7623559228023Released space at 7623591715040Pressed space at 7623591835237
Luis Soeiro
A: 

This question is duplicated here.

In that question, a link to the Sun bug parade is given, where some workaround is suggested.

I've made a hack implemented as an AWTEventListener that can be installed at the start of the application.

Basically, observe that the time between the RELEASED and the subsequent PRESSED is small - actually, it is 0 millis. Thus, you can use that as a measure: Hold the RELEASED for some time, and if a new PRESSED comes right after, then swallow the RELEASED and just handle the PRESSED (And thus you will get the same logic as on Windows, which obviously is the correct way). However, watch for the wrap-over from one millisecond to the next (I've seen this happen) - so use at least 1 ms to check. To account for lags and whatnots, some 20-30 milliseconds probably won't hurt.

stolsvik