Search code examples
javamacoskey-bindingsdiacritics

Stop OSX diacritics disabling KeyBindings in Java for all users?


(Note: I am aware of this question in which the user would have to enter a Terminal command to fix this issue, but I would prefer a solution in which the solution can be put into the application.)

To explain, I am using KeyBindings in a Java application; however, if one holds a key like a, e, i, o, u, n, s, etc., the diacritic menu OSX uses somehow completely disables key input.

It does not, however, affect mouse input if that is relevant.

Here is some sample code in which the problem can be demonstrated. If you press and hold any of the aforementioned keys on OSX for approximately one second or more, the KeyBindings stop working entirely. (I recommend holding the key down for more just to be sure, though.

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

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.WindowConstants;

public class Test extends JPanel implements ActionListener {
    private static final long serialVersionUID = 1L;

    private static JFrame frame;

    private static boolean up = false, down = false, left = false, right = false;

    private static int x = 275, y = 275;

    public static void main(String[] args) {
        Test t = new Test();
        t.setBounds(0, 0, 1200, 600);
        t.setVisible(true);

        Timer repaintTimer = new Timer(2, t);

        frame = new JFrame();
        frame.setSize(600, 600);

        setUpKeyActions(t);

        frame.add(t);

        Dimension dim = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocation((dim.width - frame.getWidth()) / 2, (dim.height - frame.getHeight()) / 2);
        frame.getContentPane().setLayout(null);
        frame.setAlwaysOnTop(true);
        frame.setResizable(false);

        repaintTimer.start();

        frame.setVisible(true);
        frame.requestFocus();
    }

    private static void setUpKeyActions(Test t) {
        int condition = WHEN_IN_FOCUSED_WINDOW;

        new KeyAction(t, condition, KeyEvent.VK_UP, 0, false) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                up = true;
            }

        };

        new KeyAction(t, condition, KeyEvent.VK_UP, 0, true) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                up = false;
            }

        };

        new KeyAction(t, condition, KeyEvent.VK_LEFT, 0, false) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                left = true;
            }

        };

        new KeyAction(t, condition, KeyEvent.VK_LEFT, 0, true) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                left = false;
            }

        };

        new KeyAction(t, condition, KeyEvent.VK_RIGHT, 0, false) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                right = true;
            }

        };

        new KeyAction(t, condition, KeyEvent.VK_RIGHT, 0, true) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                right = false;
            }

        };

        new KeyAction(t, condition, KeyEvent.VK_DOWN, 0, false) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                down = true;
            }

        };

        new KeyAction(t, condition, KeyEvent.VK_DOWN, 0, true) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                down = false;
            }

        };

    }

    private static abstract class KeyAction extends AbstractAction {
        private static final long serialVersionUID = 1L;

        KeyAction(JComponent component, int condition, int keyCode, int modifiers, boolean onKeyRelease) {
            InputMap inputMap = component.getInputMap(condition);
            ActionMap actionMap = component.getActionMap();
            KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers, onKeyRelease);
            inputMap.put(keyStroke, keyStroke.toString());
            actionMap.put(keyStroke.toString(), this);
        }

    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if(up)
            y -= 1;
        if(down)
            y += 1;
        if(right)
            x += 1;
        if(left)
            x -= 1;
        if(x < 0)
            x = 0;
        else if(x > frame.getWidth() - 30)
            x = frame.getWidth() - 30;
        if(y < 0)
            y = 0;
        else if(y > frame.getHeight() - 50)
            y = frame.getHeight() - 50;
        g.drawRect(x, y, 30, 30);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        frame.repaint();
    }

}

Solution

  • To answer the question I was having, the issue was the Java version.

    I had JDK 1.8.0_151, but apparently this issue was fixed in some iteration up to JDK 1.8.0_172.

    So, to make sure this issue does not happen to the users of my application, I will require JDK 1.9 or above using org.apache.commons.lang3.SystemUtils and use the isJavaVersionAtLeast(JavaVersion requiredVersion) method from this answer.

    Update:

    I actually plan to use the method I got from my question here since it allows me not to have to require JRE 1.9 but instead allows me to make the minimum 1.8.0_172.