Direct access to the keyboard?
Hi all -
I'm having some trouble because I'm making an application that I want to run on all platforms. I'm creating it on OS X, but I want to have it run on Linux as well.
The problem, which I'm sure at least some of you are aware, is that in Linux (at least in KDE) the 'key repeat' function triggers 'key released' events as well as 'key pressed' events even when the user is holding down the key. This makes it very hard to do (seemingly) simple things like determine how long the user has been holding down a key.
I'm aware of some of the work arounds, so lets just get that out of the way:
1. Turn off 'key repeat'. That just isn't realistic if you want to make a commercial application you can't expect users to be changing their system settings to just run your application when they don't have to for any other application. C++ people would make fun of me ;)
2. Use a timer to figure out if the last 'key released' event was a real one. This doesn't work because it is hard to know what value the user has their key repeat set to, you don't know if the user is holding down the key or pressing it rapidly, and it adds extra overhead for OS's that don't have this problem.
3. Post the event to your internal actions and then consume it. Again, no way to tell if the user is holding down the key or not and you are somewhat dependant on the key repeat being very rapid.
So I figure that if I can at least just get an array or something of the current state of the keyboard, then I can figure all this out for myself. I'm asking this in the 'games' section because I know this is a common way to do games programming (in DirectX for example). I've looked at some of the Xith3D and similar boards, but everyone there seems to be happy with one of the previously mentioned solutions.
Is there a real solution to this? ...or if not, can I at least look at the key state myself and just deal with it on my own?
Thanks!
[2007 byte] By [
insectilea] at [2007-10-2 9:29:16]

I'm in a very similar situation. The application needs to know when the key was released in order to stop processing the key immediately, instead of lagging behind the user and processing dozens of repeated keystrokes still in the queue.
The difficulty is that the application must still correctly process quickly, but individually, typed keystrokes; if a key was hit 10 distinct times, the application should process each of them, without them being mistaken as repeated keys and consumed.
As insectile points out, it doesn't seem there is any good way to tell the difference between these two cases (key held down vs key hit rapidly) under Linux; I'm using Gnome and I see the same behaviour.
I would very much like to hear that there is a good solution for this.
Sun's official reponse is that this is not a bug, key events are not guarenteed to behave in a predictable (or reasonable, imo) manner. Unfortunately this seems to make key released events completely useless on a cross-platform application...
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5011907
ctpa at 2007-7-16 23:35:49 >

One possible solution might be to use sdljava (Simple DirectMedia Layer for Java): http://sdljava.sourceforge.net/docs/api/sdljava/event/SDLKeyboardEvent.html
ctpa at 2007-7-16 23:35:49 >

The trick (in pure java) is to access event dispatch times. For repeats, I have observed (it may not always be true on all platforms) that the release and press have the same dispatch time. When a release is recieved, save its time and add a new process to the event queue. When a press is called, if its time is the same as the saved time, ignore it and set a "ignore" flag. The added process in the event queue then checks to see if the flag was set and, if not, process the event.
Or, in code,
import javax.swing.JFrame;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
class IgnoreRepeats extends JFrame implements KeyListener {
long timeup;
public IgnoreRepeats(String name) {
super(name);
super.addKeyListener(this);
super.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack(); setVisible(true);
}
public static void main(String[] _) {
new IgnoreRepeats("Test Window");
}
public int serialVersionUID() { return 1; }
public void keyReleased(final KeyEvent e) {
timeup = e.getWhen();
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
if (0 != timeup)
System.out.println("Key "+e.getKeyCode()+" released");
}
});
}
public void keyPressed(final KeyEvent e) {
if (timeup == e.getWhen()) {
timeup = 0;
return;
}
System.out.println("Key "+e.getKeyCode()+" released");
}
public void keyTyped(KeyEvent e) {}
}
Thanks, Luther.
That solution works well on Linux, but fails on Mac OS X. (Have not tested on Windows).
I can't believe there's no way to do something as basic as actually knowing when the user has a key pressed down! This seems like a pretty big hole, and I imagine there must be some real workaround somewhere...
Here's another hack, but it only works in video games, etc., where you have a separate thread running (and SOMETIMES it may still fail due to synchronization issues):
http://forums.dreamincode.net/showtopic14233.htm
My solution, which may or may not work on any given OS, but worked when tested on Linux and Mac OS X:
long lastUpTime=0;
char lastUpChar='a';
public void keyPressed(KeyEvent e) {
System.out.println("lastUpTime="+lastUpTime);
// it's zero under linux, it's <6 for Mac OS, so we just use 10ms tolerance
if (lastUpTime-e.getWhen()<10 && lastUpChar==e.getKeyChar()) {
System.out.println(" think it was FAKE released");
lastUpTime = 0;
lastUpChar='a';
return;
} else if (lastUpTime!=0) {
// okay, the key was released. log at that time.
System.out.println("Key released: \""+lastUpChar+"\" a while ago, at "+lastUpTime);
lastUpChar='a';
lastUpTime=0;
return;
}
System.out.println("Key pressed: "+e.getKeyChar());
lastUpTime=0;
lastUpChar='a';
...
public void keyReleased(KeyEvent e) {
if (e.getKeyChar()==' ' || e.getKeyChar()=='b') {
System.out.println("Tentative key realeased: "+e.getKeyChar());
lastUpTime=e.getWhen();
lastUpChar=e.getKeyChar();
}
}});
