简体   繁体   English

Linux上的Java Keylistener

[英]java keylistener on linux

I'm trying to write a game in java3d on Linux and for that I need a proper KeyListener. 我正在尝试在Linux上的java3d中编写游戏,为此,我需要适当的KeyListener。 Did anyone of you know how to do it? 你们当中有人知道怎么做吗? I'm currently using following code, I found somewhere on the net. 我当前正在使用以下代码,我在网上发现了某个地方。 It's working pretty good, holding down just one key, but as soon, as I press more than one (like space and w) it will do unexpected things... 按下一个键就可以了,但是当我按下多个键(例如空格键和w键)时,它会做意想不到的事情……

public class RepeatingReleasedEventsFixer implements AWTEventListener {

    private final HashMap<Integer, ReleasedAction> _map = new HashMap<Integer, ReleasedAction>();

    public void install() {
        Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
    }

    public void remove() {
        Toolkit.getDefaultToolkit().removeAWTEventListener(this);
    }

    @Override
    public void eventDispatched(AWTEvent event) {
        assert event instanceof KeyEvent : "Shall only listen to KeyEvents, so no other events shall come here";
        assert assertEDT(); // REMEMBER THAT THIS IS SINGLE THREADED, so no need for synch.

        // ?: Is this one of our synthetic RELEASED events?
        if (event instanceof Reposted) {
            // -> Yes, so we shalln't process it again.
            return;
        }

        // ?: KEY_TYPED event? (We're only interested in KEY_PRESSED and KEY_RELEASED).
        if (event.getID() == KeyEvent.KEY_TYPED) {
            // -> Yes, TYPED, don't process.
            return;
        }

        final KeyEvent keyEvent = (KeyEvent) event;

        // ?: Is this already consumed?
        // (Note how events are passed on to all AWTEventListeners even though a previous one consumed it)
        if (keyEvent.isConsumed()) {
            return;
        }

        // ?: Is this RELEASED? (the problem we're trying to fix!)
        if (keyEvent.getID() == KeyEvent.KEY_RELEASED) {
            // -> Yes, so stick in wait
            /**
             * Really just wait until "immediately", as the point is that the subsequent PRESSED shall already have been
             * posted on the event queue, and shall thus be the direct next event no matter which events are posted
             * afterwards. The code with the ReleasedAction handles if the Timer thread actually fires the action due to
             * lags, by cancelling the action itself upon the PRESSED.
             */
            final Timer timer = new Timer(2, null);
            ReleasedAction action = new ReleasedAction(keyEvent, timer);
            timer.addActionListener(action);
            timer.start();

            _map.put(Integer.valueOf(keyEvent.getKeyCode()), action);

            // Consume the original
            keyEvent.consume();
        }
        else if (keyEvent.getID() == KeyEvent.KEY_PRESSED) {
            // Remember that this is single threaded (EDT), so we can't have races.
            ReleasedAction action = _map.remove(Integer.valueOf(keyEvent.getKeyCode()));
            // ?: Do we have a corresponding RELEASED waiting?
            if (action != null) {
                // -> Yes, so dump it
                action.cancel();
            }
            // System.out.println("PRESSED: [" + keyEvent + "]");
        }
        else {
            throw new AssertionError("All IDs should be covered.");
        }
    }

    /**
     * The ActionListener that posts the RELEASED {@link RepostedKeyEvent} if the {@link Timer} times out (and hence the
     * repeat-action was over).
     */
    private class ReleasedAction implements ActionListener {

        private final KeyEvent _originalKeyEvent;
        private Timer _timer;

        ReleasedAction(KeyEvent originalReleased, Timer timer) {
            _timer = timer;
            _originalKeyEvent = originalReleased;
        }

        void cancel() {
            assert assertEDT();
            _timer.stop();
            _timer = null;
            _map.remove(Integer.valueOf(_originalKeyEvent.getKeyCode()));
        }

        @Override
        public void actionPerformed(@SuppressWarnings ("unused") ActionEvent e) {
            assert assertEDT();
            // ?: Are we already cancelled?
            // (Judging by Timer and TimerQueue code, we can theoretically be raced to be posted onto EDT by TimerQueue,
            // due to some lag, unfair scheduling)
            if (_timer == null) {
                // -> Yes, so don't post the new RELEASED event.
                return;
            }
            // Stop Timer and clean.
            cancel();
            // Creating new KeyEvent (we've consumed the original).
            KeyEvent newEvent = new RepostedKeyEvent((Component) _originalKeyEvent.getSource(),
                    _originalKeyEvent.getID(), _originalKeyEvent.getWhen(), _originalKeyEvent.getModifiers(),
                    _originalKeyEvent.getKeyCode(), _originalKeyEvent.getKeyChar(), _originalKeyEvent.getKeyLocation());
            // Posting to EventQueue.
            Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(newEvent);
            // System.out.println("Posted synthetic RELEASED [" + newEvent + "].");
        }
    }

    /**
     * Marker interface that denotes that the {@link KeyEvent} in question is reposted from some
     * {@link AWTEventListener}, including this. It denotes that the event shall not be "hack processed" by this class
     * again. (The problem is that it is not possible to state "inject this event from this point in the pipeline" - one
     * have to inject it to the event queue directly, thus it will come through this {@link AWTEventListener} too.
     */
    public interface Reposted {
        // marker
    }

    /**
     * Dead simple extension of {@link KeyEvent} that implements {@link Reposted}.
     */
    public static class RepostedKeyEvent extends KeyEvent implements Reposted {
        public RepostedKeyEvent(@SuppressWarnings ("hiding") Component source, @SuppressWarnings ("hiding") int id,
                long when, int modifiers, int keyCode, char keyChar, int keyLocation) {
            super(source, id, when, modifiers, keyCode, keyChar, keyLocation);
        }
    }

    private static boolean assertEDT() {
        if (!EventQueue.isDispatchThread()) {
            throw new AssertionError("Not EDT, but [" + Thread.currentThread() + "].");
        }
        return true;
    }
}

I can't be the only one who still runs into this - meanwhile 15 yo - problem and don't want to use timers... 我不能成为唯一一个仍然遇到这个问题的人-同时15岁-问题并且不想使用计时器...

EDIT: What this code is doing is fix the known problem on any Linux distri, where you add a simple KeyListener, which handles keyDowns, but invokes keyReleased Event repeatedly. 编辑:此代码正在做的是解决任何Linux发行版上的已知的问题,您在其中添加了一个简单的KeyListener,它可以处理keyDowns,但是会反复调用keyReleased事件。 To clearify my problem here a simple example 为了解决我的问题,这里有一个简单的例子

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;

public class Test5 extends JFrame{

    public Test5() {
        addKeyListener(new KeyListener() {
            boolean keydown = false;
            @Override
            public void keyTyped(KeyEvent arg0) {
                // TODO Auto-generated method stub

            }

            @Override
            public void keyReleased(KeyEvent arg0) {
                keydown = false;
                System.out.println("keyup");
            }

            @Override
            public void keyPressed(KeyEvent arg0) {
                if (keydown){
                    System.out.println("key is down");
                } else {
                    System.out.println("key not down");
                }
                keydown = true;
            }
        });

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(400, 400);
        setVisible(true);
        //new RepeatingReleasedEventsFixer().install(); // This line will fix it for one key pressed
    }
    public static void main(String[] args) {
        new Test5();
    }

}

The output without the line being commented out: 没有注释掉该行的输出:

key not down
keyup
key not down
keyup
key not down
keyup
key not down
keyup
key not down
keyup

otherwise: 除此以外:

key not down
key is down
key is down
key is down
key is down
key is down
key is down
key is down
key is down
key is down
keyup

Btw. 顺便说一句。 How come, that it's not beeing fixed by now? 怎么还没有被解决呢?

EDIT: I tried the KeyBindings, as suggested, where it comes to these problems: 编辑:我尝试了KeyBindings,如建议的,涉及到这些问题:

public class Test5 extends JFrame{
    long timestamp = 0;
    public Test5() {
        ((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('a'), "a");
        ((JComponent)getComponent(0)).getActionMap().put("a", new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("time: "+(System.currentTimeMillis()-timestamp));
                timestamp = System.currentTimeMillis();
            }
        });

        ((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('s'), "s");
        ((JComponent)getComponent(0)).getActionMap().put("s", new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                System.out.println("s");
            }
        });

        ((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('d'), "d");
        ((JComponent)getComponent(0)).getActionMap().put("d", new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                System.out.println("d");
            }
        });
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(400, 400);
        setVisible(true);
        new RepeatingReleasedEventsFixer().install(); // This line will fix it for one key pressed
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        new Test5();
    }

Holding down "a" will give me following output: 按住“ a”将给我以下输出:

time: 4171
time: 501
time: 30
time: 30
time: 30

Where the second time is the actual problem. 第二次是实际问题所在。 It takes about 470ms too long. 大约需要470毫秒的时间。
Holding down "s" and then somewhne pressing "d" will give me that output: 按住“ s”然后再按“ d”将给我输出:

s
s
s
s
d
d
d
d
d

So I can't process two actions as the same time, so I can't use KeyBindings 所以我不能同时处理两个动作,所以不能使用KeyBindings

This is not an answer, it is a long comment with a picture and some explanations. 这不是答案,而是带有图片和一些解释的冗长评论。

I used your Test5 (without RepeatingReleasedEventsFixer ) to hold down a and measure the time responses. 我用了您的Test5 (没有RepeatingReleasedEventsFixer )来按住a并测量时间响应。 The output is of the form 输出形式为

time: t1
time: t2
time: t3
time: t3
time: t3
...

t1 is meaningless since it depends on the current time and has nothing to do with response time (you also seem to ignore it). t1毫无意义,因为它取决于当前时间,并且与响应时间无关(您似乎也忽略了它)。

t2 is the time it takes for the OS to realize that you're holding the key for repeated input. t2是OS意识到您按住要重复输入的键所花费的时间。

t3 is the "sample time" of the held key, or a discretization of the input. t3按住键的“采样时间”,或者输入的离散化。

I'm using Windows where I have the following control panel options: 我正在使用Windows,其中具有以下控制面板选项:

在此处输入图片说明

Repeat delay allows me to set t2 between ~257 (short) and ~1050 (long). 重复延迟允许我将t2设置在〜257(短)和〜1050(长)之间。

Repeat rate allows me to set t3 between ~407 (slow) and ~37 (fast). 重复频率使我可以将t3设置在〜407(慢)和〜37(快)之间。

For Linux, you'll have to consult someone / somewhere on how to change these values if you don't already know how to. 对于Linux,如果您尚不知道如何更改这些值,则必须咨询某人/某处。

As for using multiple keys, see this question and answer and the excellent link within (especially the "Motion With Multiple Keys Pressed" section). 至于使用多个键,请参阅此问题和答案以及其中的出色链接(尤其是“按下多个键的运动”部分)。 It's a short tutorial and analysis of key bindings and key listeners, similar to the one I sent you to on this site. 这是一个简短的教程,对键绑定和键侦听器进行了分析,类似于我在此站点上发送给您的内容。

Key bindings will always be preferred over key listeners unless maybe there is some very low level thing you want to do. 除非总是要进行一些非常低级的操作,否则始终优先使用键绑定而不是键侦听器。

After days of researching and putting stuff together, I ended up writing my own Listener combined with a KeyEventDispatcher, here is the code for someone running into the same problem. 经过数天的研究和整理,我最终编写了自己的Listener和KeyEventDispatcher结合使用,这是遇到相同问题的人员的代码。 It can and should be optimized, but is working for now: 可以并且应该对其进行优化,但是目前可以使用:

Klass to test if a specific key is pressed: 进行测试是否按下特定键的Klass:

import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.HashMap;


public class IsKeyPressed {
    private static boolean wPressed = false;
    private HashMap<Integer, Boolean> keys = new HashMap<Integer, Boolean>();
    public IsKeyPressed() {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {

            @Override
            public boolean dispatchKeyEvent(KeyEvent ke) {
                synchronized (IsKeyPressed.class) {
                    switch (ke.getID()) {
                    case KeyEvent.KEY_PRESSED:
                        keys.put(ke.getKeyCode(), true);
                        break;

                    case KeyEvent.KEY_RELEASED:
                        keys.put(ke.getKeyCode(), false);
                        break;
                    }
                    return false;
                }
            }
        });
    }
    public static boolean isWPressed() {
        synchronized (IsKeyPressed.class) {
            return wPressed;
        }
    }

    public boolean isPressed(int keyCode){
        synchronized (IsKeyPressed.class) {
            if (keys == null)
                return false;
            if (keys.get(keyCode) == null)
                return false;
            return keys.get(keyCode);
        }
    }

}

Abstract class, thats beeing used for the actions. 抽象类,多数民众赞成在动作中使用。

public abstract class KeyActionListener {
    protected int keyCode;
    public KeyActionListener(int keyCode) {
        this.keyCode = keyCode;
    }
    public void setKeyCode(int keyCode){
        this.keyCode = keyCode;
    }
    public int getKeyCode(){
        return this.keyCode;
    }
    public abstract void onKeyDown();
    public abstract void onKeyUp();
    public abstract void onKeyHolding();
}

Start listening to the keys and run the actions. 开始聆听按键并执行操作。

import java.util.ArrayList;
import java.util.HashMap;

public class KeyThread extends Thread{
    private int sleep = 3;
    ArrayList<KeyActionListener> listener = new ArrayList<KeyActionListener>();
    IsKeyPressed isPressed = new IsKeyPressed();
    HashMap<KeyActionListener, Boolean> pressed = new HashMap<KeyActionListener, Boolean>();
    public KeyThread() {
        this.start();
    }
    public void run() {
        while (true){
            for (int i = 0; i < listener.size(); i++) {
                KeyActionListener curListener = listener.get(i);
                if (isPressed.isPressed(curListener.getKeyCode()) && !pressed.get(curListener)){
                    curListener.onKeyDown();
                    pressed.put(curListener, true);
                } else if(!isPressed.isPressed(curListener.getKeyCode()) && pressed.get(curListener)) {
                    curListener.onKeyUp();
                    pressed.put(curListener, false);
                } 

                if(isPressed.isPressed(curListener.getKeyCode())){
                    curListener.onKeyHolding();
                }
                try{
                    Thread.sleep(sleep);
                } catch(InterruptedException e){

                }
            }
        }
    }

    public void addKeyActionListener(KeyActionListener l){
        listener.add(l);
        pressed.put(l, false);
    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM