Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FullScreen Swing Components Fail to Receive Keyboard Input on Java 7 on Mac OS X Mountain Lion

Tags:

Update 12/21:

7u10 was recently released. Confirmed that:

  1. The issue still persists
  2. Thankfully, the workaround still functions!

Update 11/7:

And we have a workaround!

Leonid Romanov from Oracle on the openjdk.java.net mailing list provided some insight as to what's going on:

Well, although I'm not 100% sure yet, but it looks like when we enter full screen some other window becomes the first responder, hence the beep. Could you please try the following workaround: after calling setFullScreenWindow() on a frame, call setVisible(false) followed by setVisible(true). This, in theory, should restore the correct first responder.

The snippet of code that seems to work is simply:

dev.setFullScreenWindow(f); f.setVisible(false); f.setVisible(true); 

I have updated the sample code with the ability to toggle this fix on and off; it is required every time a window enters fullscreen.

In the larger context of my more complex application, I am still running into keyboard focus issues on subcomponents within the fullscreen window, where a mouse click causes my window to lose focus. (I'm guessing it's going to the undesired first responder window referenced above.) I'll report back when I have more information about this case - I cannot reproduce it in the smaller sample yet.


Update 10/31:

Major update to the sample code:

  • Includes toggle between FullScreen exclusive and Lion-style FullScreen modes
  • Listens to the KeyboardFocusManager to display the hierarchy for the currently focused component
  • Uses both input maps and KeyListeners to try to capture input

Also did some more digging with coworkers to try to isolate issues:

On one front, we tried overriding some methods in RT.jar to see if there were problems with the way the screen device is being selected. Also tried were the entry points to the Toolkit.beep() functionality to see if the alert sounds were coming from the Java side - appears not.

On another front, it was clear that not even the native side is receiving keyboard events. A coworker attributes this to a switch from an AWTView to a NSWindow in 7u6.

A selection of existing Oracle bugs has been found, which you can look up here:

  • 8000276 : [macosx] graphicsDevice.setFullScreenWindow(frame) crashes JVM
  • 8000430 : [macosx] java.awt.FileDialog issues on macosx
  • 7175707 : [macosx] PIT: 8 b43 Not running on AppKit thread issue again

Update 10/26:

Thanks to the comment by @maslovalex below regarding an Applet working on 7u5, I went back and painstakingly examined compatibility with the JDK versions for OSX:

  • 10.7.1 with 7u4: Fullscreen Works!
  • 10.7.1 with 7u5: Fullscreen Works!
  • 10.7.5 with 7u5: Fullscreen Works!
  • 10.7.5 with 7u6: Fullscreen Breaks :(

Combined with the other tests noted elsewhere, it's clear there was an issue introduced with 7u6 that remains in 7u7 and 7u9, and it affects both Lion 10.7 and Mountain Lion 10.8.

7u6 was a major milestone release, bringing full support of the JRE and JDK to Mac OS X and also including Java FX as part of the distribution. Further info is available in the Release Notes and the Roadmap. It's not tremendously surprising that such an issue could crop up as support shifts to Java FX.

The question becomes:

  1. Will Oracle fix this in a near-term release of the JDK? (If you have links to existing bugs, please include them here.)
  2. Is a workaround possible in the interim?

Other updates from today:

  • I incorporated the Apple extensions approach to fullscreen mode as an alternate path of exploration (updated sample code pending cleanup). The good news: input works! The bad news: there really don't seem to be any kiosking/isolation options.
    I tried killing the Dock - directly or with an App - as I understand the Dock is responsible for Command-Tab app switching, Mission Control, and Launch Pad, only to find out that it's responsible for the handling of fullscreen apps as well! As such, the Java calls become non-functional and never return.
    If there's a way to disable Command-Tab (and Mission Control and Launchpad and Spaces) without affecting the Dock's fullscreen handling, that would be extremely useful. Alternatively, one can try to remap certain keys such as Command, but that will affect the ability to use that modifier elsewhere in the program and the system itself (not exactly ideal, when you need to Command-C to copy some text).

  • I've had no luck with KeyListeners (I'm not receiving any callbacks), but I have a few more options to try.

  • Based on a coworker's suggestion, I tried ((sun.lwawt.macosx.LWCToolkit)Toolkit.getDefaultToolkit()).isApplicationActive() via reflection. The idea was that it:
    is a native method with the comment "Returns true if the application (one of its windows) owns keyboard focus.". Calls to this method were added in CPlatformWindow.java in the past few months related to focus logic. If it returns false in your test code, it's probably part of the problem.
    Unfortunately, everywhere I checked it, the method returned true. So even according to the low level system, my windows should have keyboard focus.

  • My previous optimism regarding the JAlbum fix has been dashed. The developer posted a response on their forum that explains how they simply removed proper fullscreen support on OS X while running Java 7. They have a bug into Oracle (and I'm hoping to get the bug number).


Update 10/25:

I have now also tried Java 7u9 on Lion 10.7.4 and have seen the exact same issue, so it's JDK- not OS-specific.

The core question has become whether you can embed in a fullscreen window core Swing Components that have default handling for keyboard input (JTextField/JTextArea or even editable combo boxes) and expect them to behave normally (without having to resort to rebuilding their basic key bindings manually). Also in question is whether other stalwarts of windowed layouts, such as using tab for focus traversal, should work.

The ideal goal would be to have the ability take a windowed Swing app with all of its buttons, tabs, fields, etc. and run it in fullscreen exclusive/kiosk mode with most functionality intact. (Previously, I have seen that Dialog pop ups or ComboBox drop downs do not function in fullscreen on Java 6 on OS X, but other Components behave fine.)

I'll be looking into the eawt FullScreen capabilities, which will be interesting if they support kiosk lock down options, such as eliminating Command-Tab application switching.


Original Question:

I have a Swing app that for years has supported FullScreen (exclusive) mode on Mac OS X up through Java 6. I've been doing compatibility testing with the latest Mountain Lion release (10.8.2 Supplemental) and Oracle's JDK 7 and noticed a glaring issue while in that mode: mouse movement and clicks work fine, but keyboard input is not delivered to the components.

(I've narrowed this down in a test case below to not being able to type into a simple JTextField while in fullscreen mode.)

One symptom is that each key press results in a system beep, as if the OS is disallowing keyboard events from being delivered to the application.

Separately, my application has an exit hook installed, and the Command-Q combo will trigger that hook - it's clear that the OS is listening to standard key combos.

I have tested this separately on three different Macs with various installs:

  • On Apple Java 6u35 and 6u37: both windowed and fullscreen modes receive input.
  • On Oracle Java 7u7 and 7u9: windowed mode works as expected while fullscreen has the symptoms above.

This may have been previously reported: Java Graphics Full Screen Mode not Registering Keyboard Input. However, that question is not specific about the Java version or platform.

Additional searching has turned up a separate fullscreen option introduced in Lion: Fullscreen feature for Java Apps on OSX Lion. I have yet to try using this approach, as keyboard input seems integral to the target use cases of FullScreen Exclusive mode, such as games.

There is some mention in the JavaDoc for this mode that input methods might be disabled. I tried to call the suggested Component.enableInputMethods(false), but it seemed to have no effect.

I'm somewhat optimistic that there's a solution to this issue based on an entry in the release notes of a Java app I came across (JAlbum). A stated fix for 10.10.6: "Keyboard support wasn't working when running the full screen slide show on Mac and Java 7"

My test case is below. It is lightly modified from the second example in this issue (which, unmodified, also exhibits my problem): How to handle events from keyboard and mouse in full screen exclusive mode in java? In particular, it adds a button to toggle fullscreen.

import java.lang.reflect.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.beans.*;  /** @see https://stackoverflow.com/questions/13064607/ */ public class FullScreenTest extends JPanel {     private GraphicsDevice dev = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();     private JFrame f = new JFrame("FullScreenTest");      private static final String EXIT = "Exit";     private Action exit = new AbstractAction(EXIT) {         @Override         public void actionPerformed(ActionEvent e) {             Object o = dev.getFullScreenWindow();             if(o != null) {                 dev.setFullScreenWindow(null);             }             f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING));         }     };     private JButton exitBTN = new JButton(exit);      private JTextField jtf = new JTextField("Uneditable in FullScreen with Java7u6+ on Mac OS X 10.7.3+");      private JLabel keystrokeLabel = new JLabel("(Last Modifier+Key Pressed in JTextField)");      private JLabel jtfFocusLabel = new JLabel("(JTextField Focus State)");      private JLabel focusLabel = new JLabel("(Focused Component Hierarchy)");      private JCheckBox useOSXFullScreenCB = new JCheckBox("Use Lion-Style FullScreen Mode");      private JCheckBox useWorkaroundCB = new JCheckBox("Use Visibility Workaround to Restore 1st Responder Window");      private static final String TOGGLE = "Toggle FullScreen (Command-T or Enter)";      private Action toggle = new AbstractAction(TOGGLE) {         @Override         public void actionPerformed(ActionEvent e) {             Object o = dev.getFullScreenWindow();             if(o == null) {                 f.pack();                  /**                   * !! Neither of these calls seem to have any later effect.                    * One exception: I have a report of a                   * Mini going into an unrecoverable black screen without setVisible(true);                    * May be only a Java 6 compatibility issue.  !!                  */                 //f.setVisible(true);                 //f.setVisible(false);                  if(!useOSXFullScreenCB.isSelected()) {                     // No keyboard input after this call unless workaround is used                     dev.setFullScreenWindow(f);                      /**                      * Workaround provided by Leonid Romanov at Oracle.                      */                     if(useWorkaroundCB.isSelected()) {                         f.setVisible(false);                         f.setVisible(true);                         //Not necessary to invoke later...                         /*SwingUtilities.invokeLater(new Runnable() {                             public void run() {                                 f.setVisible(false);                                 f.setVisible(true);                             }                         });*/                     }                 }                 else {                     toggleOSXFullscreen(f);                 }             }             else {                 dev.setFullScreenWindow(null);                 f.pack();                 f.setVisible(true);             }              isAppActive();         }     };     private JButton toggleBTN = new JButton(toggle);      public FullScreenTest() {                     // -- Layout --         this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));          exitBTN.setAlignmentX(JComponent.CENTER_ALIGNMENT);         exitBTN.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));         this.add(exitBTN);          jtf.setAlignmentX(JComponent.CENTER_ALIGNMENT);         jtf.setMaximumSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE));         this.add(jtf);          keystrokeLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT);         keystrokeLabel.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));         keystrokeLabel.setHorizontalAlignment(SwingConstants.CENTER);         keystrokeLabel.setForeground(Color.DARK_GRAY);         this.add(keystrokeLabel);          jtfFocusLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT);         jtfFocusLabel.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));         jtfFocusLabel.setHorizontalAlignment(SwingConstants.CENTER);         jtfFocusLabel.setForeground(Color.DARK_GRAY);         this.add(jtfFocusLabel);          focusLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT);         focusLabel.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));         focusLabel.setHorizontalAlignment(SwingConstants.CENTER);         focusLabel.setForeground(Color.DARK_GRAY);         this.add(focusLabel);          useOSXFullScreenCB.setAlignmentX(JComponent.CENTER_ALIGNMENT);         useOSXFullScreenCB.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));         useOSXFullScreenCB.setHorizontalAlignment(SwingConstants.CENTER);         this.add(useOSXFullScreenCB);          useWorkaroundCB.setAlignmentX(JComponent.CENTER_ALIGNMENT);         useWorkaroundCB.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));         useWorkaroundCB.setHorizontalAlignment(SwingConstants.CENTER);         this.add(useWorkaroundCB);          toggleBTN.setAlignmentX(JComponent.CENTER_ALIGNMENT);         toggleBTN.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));         this.add(toggleBTN);          f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);         f.setResizable(false);         f.setUndecorated(true);         f.add(this);         f.pack();          enableOSXFullscreen(f);          // -- Listeners --          // Default BTN set to see how input maps respond in fullscreen         f.getRootPane().setDefaultButton(toggleBTN);          // Explicit input map test with Command-T toggle action from anywhere in the window         this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(                 KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),                  toggle.getValue(Action.NAME));         this.getActionMap().put(toggle.getValue(Action.NAME), toggle);          // KeyListener test         jtf.addKeyListener(new KeyAdapter() {                                                                                                                                                                                                                                                                 public void keyPressed(KeyEvent e) {                                                                                                                                                                                                                                                                   String ktext = "KeyPressed: "+e.getKeyModifiersText(e.getModifiers()) + "_"+ e.getKeyText(e.getKeyCode());                 keystrokeLabel.setText(ktext);                 System.out.println(ktext);             }         });          // FocusListener test         jtf.addFocusListener(new FocusListener() {             public void focusGained(FocusEvent fe) {                 focused(fe);             }             public void focusLost(FocusEvent fe) {                 focused(fe);             }             private void focused(FocusEvent fe) {                 boolean allGood = jtf.hasFocus() && jtf.isEditable() && jtf.isEnabled();                 jtfFocusLabel.setText("JTextField has focus (and is enabled/editable): " + allGood);                 isAppActive();             }         });          // Keyboard Focus Manager         KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();         focusManager.addPropertyChangeListener(new PropertyChangeListener() {             public void propertyChange(PropertyChangeEvent e) {                 if (!("focusOwner".equals(e.getPropertyName()))) return;                 Component comp = (Component)e.getNewValue();                 if(comp == null) {                     focusLabel.setText("(No Component Focused)");                     return;                 }                 String label = comp.getClass().getName();                 while(true) {                     comp = comp.getParent();                     if(comp == null) break;                     label = comp.getClass().getSimpleName() + " -> " + label;                 }                 focusLabel.setText("Focus Hierarchy: " + label);                 isAppActive();             }         });     }      /**      * Hint that this Window can enter fullscreen.  Only need to call this once per Window.      * @param window      */     @SuppressWarnings({"unchecked", "rawtypes"})     public static void enableOSXFullscreen(Window window) {         try {             Class util = Class.forName("com.apple.eawt.FullScreenUtilities");             Class params[] = new Class[]{Window.class, Boolean.TYPE};             Method method = util.getMethod("setWindowCanFullScreen", params);             method.invoke(util, window, true);         } catch (ClassNotFoundException e1) {         } catch (Exception e) {             System.out.println("Failed to enable Mac Fullscreen: "+e);         }     }      /**      * Toggle OSX fullscreen Window state. Must call enableOSXFullscreen first.      * Reflection version of: com.apple.eawt.Application.getApplication().requestToggleFullScreen(f);      * @param window      */     @SuppressWarnings({"unchecked", "rawtypes"})     public static void toggleOSXFullscreen(Window window) {         try {             Class appClass = Class.forName("com.apple.eawt.Application");              Method method = appClass.getMethod("getApplication");             Object appInstance = method.invoke(appClass);              Class params[] = new Class[]{Window.class};             method = appClass.getMethod("requestToggleFullScreen", params);             method.invoke(appInstance, window);         } catch (ClassNotFoundException e1) {         } catch (Exception e) {             System.out.println("Failed to toggle Mac Fullscreen: "+e);         }     }      /**      * Quick check of the low-level window focus state based on Apple's Javadoc:      *  "Returns true if the application (one of its windows) owns keyboard focus."      */     @SuppressWarnings({"unchecked", "rawtypes"})     public static void isAppActive() {         try {             Class util = Class.forName("sun.lwawt.macosx.LWCToolkit");             Method method = util.getMethod("isApplicationActive");             Object obj = method.invoke(Toolkit.getDefaultToolkit());             System.out.println("AppActive: "+obj);         } catch (ClassNotFoundException e1) {         } catch (Exception e) {             System.out.println("Failed to check App: "+e);         }     }      public static void main(String[] args) {         System.out.println("Java Version: " + System.getProperty("java.version"));         System.out.println("OS Version: " + System.getProperty("os.version"));          SwingUtilities.invokeLater(new Runnable() {             @Override             public void run() {                 FullScreenTest fst = new FullScreenTest();                 if(!fst.dev.isFullScreenSupported()) {                     System.out.println("FullScreen not supported on this graphics device.  Exiting.");                     System.exit(0);                 }                 fst.toggle.actionPerformed(null);             }         });     } } 
like image 824
Vermillion002 Avatar asked Oct 25 '12 08:10

Vermillion002


1 Answers

This is because the component to which you added the other has now lost focus, you can fix this by either:

  • calling requestFocus() on the component instance to which you add KeyBindings

or

  • alternatively use JComponent.WHEN_IN_FOCUSED_WINDOW with KeyBindings:

    component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, 0),                         "doSomething"); component.getActionMap().put("doSomething",                          anAction); 

Reference:

  • How to Use Key Bindings
like image 171
David Kroukamp Avatar answered Oct 07 '22 09:10

David Kroukamp