Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Swing KeyBindings stop working only on Mac

I'm working on a game using Swing and I'm using KeyBindings for input from the keyboard.

I'm having issues where the KeyBindings stop responding. It happens every time I run the application, but as far as I can tell, it's not when a certain chain of events occurs. The KeyBindings just stop receiving input from the keyboard. I also use mouse input, which continues to work, so I know it's not related to input in general.

Some things I've tried:

  • made sure my object was not garbage collected
  • looked for a certain reason the issue occurs (ex: after a certain amount of time, a certain key combination pressed), to which I could find none
  • tried to use a KeyListener instead

None of these worked.

I then copied the project (without changing any code) to a windows machine, and after testing, I could not get the problem to occur once. I didn't paste code here because I'm 99% positive this is not related to my code, but related to the operating system it's running on.

Is this a problem with the macOS, or the JDK for Mac, or something else I don't know about?

I'm using JDK version 8 update 112, the computer is running macOS Sierra version 10.12.2.

It seems someone else had the same problem I now do, but they never got an answer.

EDIT

Here's a code example where the problem occurs:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

//an example of the problem where the keyboard stops receiving input randomly
public class ProblemExample extends JPanel {
    private static final long serialVersionUID = 1L;

    private int xPos = 200, yPos = 200;
    private boolean wKey, aKey, sKey, dKey;
    private BufferedImage image; //sprite

    public ProblemExample() {
        JFrame frame = new JFrame();
        frame.add(this);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        //makes the sprite a red square
        image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
        int[] redPixels = new int[50 * 50];
        for (int i = 0; i < redPixels.length; i++) {
            redPixels[i] = 0xffff0000;
        }
        image.setRGB(0, 0, 50, 50, redPixels, 0, 50);
        initializeKeys();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(800, 600);
    }

    //sets up Key Bindings
    private void initializeKeys() {
        final String W = "W",
                     A = "A", 
                     S = "S", 
                     D = "D",
                     PRESSED = "PRESSED",
                     RELEASED = "RELEASED";

        InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = this.getActionMap();

        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), W + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), A + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), S + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), D + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), W + RELEASED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), A + RELEASED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), S + RELEASED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), D + RELEASED);

        Action wActionPressed = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                wKey = true;
            }
        };
        Action aActionPressed = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                aKey = true;
            }
        };
        Action sActionPressed = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                sKey = true;
            }
        };
        Action dActionPressed = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dKey = true;
            }
        };
        Action wActionReleased = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                wKey = false;
            }
        };
        Action aActionReleased = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                aKey = false;
            }
        };
        Action sActionReleased = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                sKey = false;
            }
        };
        Action dActionReleased = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dKey = false;
            }
        };

        actionMap.put(W + PRESSED, wActionPressed);
        actionMap.put(A + PRESSED, aActionPressed);
        actionMap.put(S + PRESSED, sActionPressed);
        actionMap.put(D + PRESSED, dActionPressed);
        actionMap.put(W + RELEASED, wActionReleased);
        actionMap.put(A + RELEASED, aActionReleased);
        actionMap.put(S + RELEASED, sActionReleased);
        actionMap.put(D + RELEASED, dActionReleased);
    }

    public void loop() {
        if (wKey) yPos -= 5;
        if (aKey) xPos -= 5;
        if (sKey) yPos += 5;
        if (dKey) xPos += 5;
        repaint();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(image, xPos, yPos, null);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                ProblemExample example = new ProblemExample();
                Timer timer = new Timer(60, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        example.loop();
                    }
                });
                timer.start();
            }
        });
    }

}
like image 868
kneedhelp Avatar asked Dec 30 '16 09:12

kneedhelp


People also ask

When to use key bindings in JComponent?

The JComponent class supports key bindings as a way of responding to individual keys typed by a user. Here are some examples of when key bindings are appropriate:

How do you bind a key to an action in Java?

How Key Bindings Work. The key binding support provided by JComponent relies on the InputMap and ActionMap classes. An input map binds key strokes to action names, and an action map specifies the action corresponding to each action name. Technically, you don't need to use action names in the maps; you can use any object as the "key" into the maps.

What are some examples of key bindings?

Here are some examples of when key bindings are appropriate: You're creating a custom component and want to support keyboard access to it. For example, you might want the component to react when it has the focus and the user presses the Space key. You want to override the behavior of an existing key binding.

What is the best alternative to key bindings?

An alternative to key bindings is using key listeners. Key listeners have their place as a low-level interface to keyboard input, but for responding to individual keys key bindings are more appropriate and tend to result in more easily maintained code.


2 Answers

You might think this is a bug in MAC, while it is not Because MAC has the feature of secondary keys.
As you are the user of MAC, I hope you are aware of this feature. This feature might be the the cause of your problem.
The Secondary key feature of MAC : is used where holding down a letter key will display variations of that letter, like holding down “u” to get “ü.” This comes in handy when spelling non-English words.

There’s a simple way to take control and change the behavior of long key presses to accommodate your needs.

Open the Terminal app and write:

defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false

Then restart any open app in which you want this setting to activate.

REVERTING BACK: Just,simply add true in place of false to the previous command like this:

defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool true

UPDATE:
You can speed up the rate of key repeats or decrease the delay before a held key starts repeating, by going to the System Preferences, and make changes under the keyboard header.

like image 156
Tahir Hussain Mir Avatar answered Nov 15 '22 16:11

Tahir Hussain Mir


Besides what Tahir Hussain Mir suggest about changing the Mac key settings, I find the problem lies with the looping, it'd be more efficient to implement it in a event-driven way, I took the liberty to change your code a bit. Together with Hussain Mir suggest 's solution, it should solve your problem.

You could also handle key-repeating yourself, by e.g., starting a timer on key press and stoping it on key release, but then the key pressing behavior will differ between Windows and Mac, which isn't really the way you want to go.

package swing;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;

//an example of the problem where the keyboard stops receiving input randomly
public class SOMacKeyBindings extends JPanel
{
    private BufferedImage image; //sprite
    private Point point = new Point(200, 200);
    private int steps = 5;

    private class KeyAction extends AbstractAction
    {
        private Runnable runnable;

        public KeyAction(Runnable runnable)
        {
            this.runnable = runnable;
        }

        @Override
        public void actionPerformed(ActionEvent e)
        {
            runnable.run();
        }
    }

    public SOMacKeyBindings()
    {
        JFrame frame = new JFrame();
        frame.add(this);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        //makes the sprite a red square
        image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
        int[] redPixels = new int[50 * 50];
        for (int i = 0; i < redPixels.length; i++)
        {
            redPixels[i] = 0xffff0000;
        }
        image.setRGB(0, 0, 50, 50, redPixels, 0, 50);
        initializeKeys();
    }

    @Override
    public Dimension getPreferredSize()
    {
        return new Dimension(800, 600);
    }

    //sets up Key Bindings
    private void initializeKeys()
    {
        final String W = "W",
                A = "A",
                S = "S",
                D = "D",
                PRESSED = "PRESSED";

        InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = this.getActionMap();

        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), W + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), A + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), S + PRESSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), D + PRESSED);

        actionMap.put(W + PRESSED, new KeyAction(() -> { point.y -= steps; repaint(); }));
        actionMap.put(A + PRESSED, new KeyAction(() -> { point.x -= steps; repaint(); }));
        actionMap.put(S + PRESSED, new KeyAction(() -> { point.y += steps; repaint(); }));
        actionMap.put(D + PRESSED, new KeyAction(() -> { point.x += steps; repaint(); }));
    }

    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.drawImage(image, point.x, point.y, null);
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> new SOMacKeyBindings());
    }
}
like image 26
Kevin Wang Avatar answered Nov 15 '22 15:11

Kevin Wang