2014-09-13 2 views
0

Я пытаюсь написать игру в java3d на Linux, и для этого мне нужен правильный KeyListener. Кто-нибудь из вас знал, как это сделать? В настоящее время я использую следующий код, который я нашел где-то в сети. Это работает очень хорошо, удерживая только один ключ, но как только, так как я нажимаю более чем один (например, пространство и вес) он будет делать неожиданные вещи ...java keylistener on linux

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; 
    } 
} 

Я не могу быть только один который все еще сталкивается с этим - между тем 15 лет - проблема и не хотят использовать таймеры ...

EDIT: Что делает этот код, это исправление известной проблемы на любом дистрибутиве Linux, где вы добавляете простой KeyListener, который обрабатывает keyDowns, но повторно вызывает событие KeyReleased , Чтобы clearify мою проблему здесь простой пример

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(); 
    } 

} 

Выход без линии будучи закомментирована:

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

иначе:

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. Почему, кстати, это уже не исправлено?

EDIT: Я попытался назначение клавиш, как это было предложено, где речь идет об этих проблемах:

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(); 
    } 

Удерживание "а" даст мне следующий вывод:

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

Где второй раз - актуальная проблема. Это занимает около 470 мс слишком долго.
Удерживание «с», а затем somewhne нажав «d» даст мне этот выход:

s 
s 
s 
s 
d 
d 
d 
d 
d 

Так что я не могу обрабатывать два действия одновременно, поэтому я не могу использовать сочетания клавиш

+2

Этот код представляет собой кучу чрезвычайно странной тарабарщины. Пожалуйста, объясните, что вы на самом деле хотите сделать. – Boann

+0

О, хорошо, я думал, что это был только я, который не совсем понял: D Все, что я знаю об этом коде, это то, что он работает. Ну, я хочу исправить проблему keyPress на машине Linux. Там, где вы удерживаете клавишу, вы вызываете keyPressed и keyReleased повторно. – Poehli

+0

"это вызывает keyPressed и keyReleased повторно. У вас нет методов с этими именами. – Boann

ответ

0

После нескольких дней изучения и сдачи материала я в конечном итоге написал своего собственного слушателя в сочетании с KeyEventDispatcher, вот код для кого-то, столкнувшегося с той же проблемой. Она может и должна быть оптимизирована, но работает сейчас:

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); 
     } 
    } 

} 

Абстрактный класс, вот неоспоримым используется для действий.

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(); 
} 

Начните слушать ключи и запускать действия.

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); 
    } 

} 
+1

Не будет ли цикл while (true) 'быть ресурсоемким ?'sleep (3)' очень короткий и не будет экономить много ресурсов, а высокие значения сна вызовут слишком низкую частоту дискретизации. – user1803551

+0

Да, вы абсолютно правы, поэтому я сказал, что его можно и нужно оптимизировать. Когда у меня есть еще немного времени, я подумаю о чем-то лучшем и оптимизирую его, но пока это рабочий код – Poehli

1

Это не ответ, это длинный комментарий с изображением и некоторыми пояснениями.

Я использовал ваш Test5 (без RepeatingReleasedEventsFixer) удерживать в и измерения ответов времени. Выход формы

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

t1 не имеет смысла, так как она зависит от текущего времени и не имеет ничего общего со временем отклика (вы также, кажется, игнорируют его).

t2 - это время, в течение которого ОС понимает, что вы держите ключ для повторного ввода.

t3 - это «образец времени» удерживаемого ключа или дискретизации ввода.

Я использую Windows, где у меня есть следующие варианты управления панели:

enter image description here

Повтор задержки позволяет мне установить t2 между ~ 257 (короткое замыкание) и ~ 1050 (длинный) ,

скорость повтора позволяет мне установить t3 между ~ 407 (медленно) и ~ 37 (быстрый).

Для Linux вам нужно проконсультироваться с кем-то/где-нибудь о том, как изменить эти значения, если вы еще не знаете, как это сделать.

Что касается использования нескольких ключей, см. this question and answer и отличную ссылку внутри (особенно раздел «Движение с несколькими нажатиями клавиш»). Это короткий учебник и анализ ключевых привязок и ключевых слушателей, аналогичный тому, который я отправил вам на этот сайт.

Ключевые привязки всегда будут предпочтительнее, чем ключевые слушатели, если, возможно, есть что-то очень низкоуровневое, что вы хотите сделать.

+0

Проблема существует только в системе Linux, поэтому это не исправление, но это все еще хорошее объяснение, поэтому +1 – Poehli