Search code examples
javablockingkeyeventblocked

KeyEvent keyPressed key combination is blocked


I've been struggling with the following problem for quite some hours now and can't find the solution. I'm currently working on a music/rhythm game, in which the user has to press certain keys at the right time to score points.

Now, since it's supposed to be similar to playing piano, key combinations have to be possible, aswell. There are currently 7 keys in the game (A, S, D, SPACE, J, K and L) and every combination of these are working fine except for K + L + ANY.

At first, I thought that it might not be possible to press more than 2 keys at once, but A, S, D, SPACE, J and K at the same time are no problem, but when L is pressed aswell, it simply does not respond (no KeyEvent fired).

This seems to be the problem with many other key combinations aswell. I've only found Y, X, D, T, Z, O and M (European keyboard) to be one working 7 key combination that can be pressed at once. This however is not a comfortable key combination for the player.

Here is the relevant part from my code:

package question;

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

import javax.swing.JFrame;

public class Example extends Canvas implements KeyListener {

/**
 * 
 */
private static final long serialVersionUID = 1L;

public Example() {
    JFrame frame;       
    frame = new JFrame("KeyEvent problem");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(this, BorderLayout.CENTER);
    frame.pack();       
    frame.setResizable(false);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    setFocusable(true);
    addKeyListener(this);
    requestFocusInWindow(); 
}

public void keyPressed(KeyEvent e) {
    int keyCode = e.getKeyCode();
    if(keyCode == KeyEvent.VK_A) {
        //lines.get(0).setActive(true);
    }
    else if(keyCode == KeyEvent.VK_S) {
        //lines.get(1).setActive(true);
    }
    else if(keyCode == KeyEvent.VK_D) {
    //  lines.get(2).setActive(true);
    }
    else if(keyCode == KeyEvent.VK_SPACE) {
        //lines.get(3).setActive(true);
    }
    else if(keyCode == KeyEvent.VK_J) {
        //lines.get(4).setActive(true);
    }
    else if(keyCode == KeyEvent.VK_K) {
        //lines.get(5).setActive(true);
    }
    else if(keyCode == KeyEvent.VK_L) {
        //lines.get(6).setActive(true);
    }

    System.out.println("keycode: " + keyCode + " keyChar: " + e.getKeyChar());
}

public void keyReleased(KeyEvent e) {
    int keyCode = e.getKeyCode();
    if(keyCode == KeyEvent.VK_A) {
        //lines.get(0).setActive(false);
    }
    else if(keyCode == KeyEvent.VK_S) {
        //lines.get(1).setActive(false);
    }
    else if(keyCode == KeyEvent.VK_D) {
        //lines.get(2).setActive(false);
    }
    else if(keyCode == KeyEvent.VK_SPACE) {
        //lines.get(3).setActive(false);
    }
    else if(keyCode == KeyEvent.VK_J) {
        //lines.get(4).setActive(false);
    }
    else if(keyCode == KeyEvent.VK_K) {
        //lines.get(5).setActive(false);
    }
    else if(keyCode == KeyEvent.VK_L) {
        //lines.get(6).setActive(false);
    }       
}

public void keyTyped(KeyEvent arg0) {}

public static void main(String[] args) {
    Example example = new Example();
}

}

Where lines.get(index).setActive(boolean b) just sets a flag for some graphical representation of the keys in-game. But, you can also see this in the console when pressing the buttons. They keyCode and KeyChar is spammed, when holding down each key, this works fine and the most recently pressed key is represented in the console. This does not work with J + K + L, though.

What the console of this example will show when pressing and holding A then A+S and then A+S+D:

keycode: 65 keyChar: a

keycode: 65 keyChar: a

keycode: 65 keyChar: a

keycode: 83 keyChar: s

keycode: 83 keyChar: s

keycode: 83 keyChar: s

keycode: 68 keyChar: d

keycode: 68 keyChar: d

keycode: 68 keyChar: d

Whereas J then J+K then J+K+L leads to the following:

keycode: 74 keyChar: j

keycode: 74 keyChar: j

keycode: 74 keyChar: j

keycode: 75 keyChar: k

keycode: 75 keyChar: k

keycode: 75 keyChar: k

(L is missing, eventhough it is pressed)

Is there any chance I can change this somehow? Why is this happening? I'm hoping not to have to use KeyBindings, because I'm not working with a JComponent, but Canvas at the moment.

cheers.


Solution

  • This seems to be a limitation with the OS and/or keyboard hardware, this is the test code I used with which I was only able to get six keys pressed at a time, regardless of the combinations. Tested on both Windows 7 and MacOS X Yosemite

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.FontMetrics;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Rectangle;
    import java.awt.event.ActionEvent;
    import java.awt.event.KeyEvent;
    import java.util.HashMap;
    import java.util.Map;
    import javax.swing.AbstractAction;
    import javax.swing.ActionMap;
    import javax.swing.InputMap;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.KeyStroke;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            private Map<String, Boolean> mapKeys;
    
            public TestPane() {
                mapKeys = new HashMap<>();
                mapKeys.put("A", false);
                mapKeys.put("S", false);
                mapKeys.put("D", false);
                mapKeys.put(" ", false);
                mapKeys.put("J", false);
                mapKeys.put("K", false);
                mapKeys.put("L", false);
    
                bindKey(KeyEvent.VK_A, "A");
                bindKey(KeyEvent.VK_S, "S");
                bindKey(KeyEvent.VK_D, "D");
                bindKey(KeyEvent.VK_SPACE, " ");
                bindKey(KeyEvent.VK_J, "J");
                bindKey(KeyEvent.VK_K, "K");
                bindKey(KeyEvent.VK_L, "L");
            }
    
            protected void bindKey(int keyCode, String name) {
    
                InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
                ActionMap am = getActionMap();
    
                im.put(KeyStroke.getKeyStroke(keyCode, 0, false), "pressed." + name);
                im.put(KeyStroke.getKeyStroke(keyCode, 0, true), "released." + name);
    
                am.put("pressed." + name, new KeyAction(name, true));
                am.put("released." + name, new KeyAction(name, false));
    
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
    
                FontMetrics fm = g2d.getFontMetrics();
                int charWidth = fm.charWidth('M') + 2;
                int charHeight = fm.getHeight();
                int x = (getWidth() - ((charWidth + 2) * mapKeys.size())) / 2;
                int y = (getHeight() - charHeight) / 2;
                for (String name : mapKeys.keySet()) {
                    boolean state = mapKeys.get(name);
                    int xPos = x + ((charWidth - fm.stringWidth(name))) / 2;
                    Rectangle bounds = new Rectangle(x, y, charWidth, charHeight);
                    if (state) {
                        g2d.setColor(Color.RED);
                        g2d.fill(bounds);
                    }
                    g2d.setColor(Color.BLACK);
                    g2d.draw(bounds);
                    g2d.drawString(name, xPos, y + fm.getAscent());
                    x += charWidth + 2;
                }
                g2d.dispose();
            }
    
            public class KeyAction extends AbstractAction {
    
                private String name;
                private boolean state;
    
                public KeyAction(String name, boolean state) {
                    this.name = name;
                    this.state = state;
                }
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    mapKeys.put(name, state);
                    repaint();
                }
    
            }
    
        }
    
    }