在Mac OS X Mountain Lion上,Java 7上的全屏摆动组件无法接收键盘输入。

24

更新 12/21:

最近发布了7u10版本。确认以下内容:

  1. 问题仍然存在
  2. 谢天谢地,解决方法仍然有效!

更新 11/7:

我们找到了一个解决方法!

Oracle的Leonid Romanov在openjdk.java.net邮件列表上提供了一些见解

嗯,虽然我还不能百分之百确定,但看起来当我们进入全屏模式时,其他窗口成为了第一个响应者,因此会发出哔哔声。请尝试以下解决方法:在一个窗口上调用setFullScreenWindow()后,调用setVisible(false),然后再调用setVisible(true)。理论上,这应该恢复正确的第一个响应者。

似乎有效的代码片段如下:

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

我已经更新了示例代码,使其具备开启和关闭此修复功能的能力;每当一个窗口进入全屏模式时都需要这样做。
在我更复杂的应用程序的更大背景下,我仍然遇到了全屏窗口内部子组件的键盘焦点问题,其中鼠标点击会导致我的窗口失去焦点。(我猜测它会转到上面提到的不需要的第一响应者窗口。)当我对这种情况有更多信息时,我会再次报告 - 目前我还无法在较小的示例中重现它。

更新10/31:

对示例代码进行了重大更新:

  • 包括在全屏独占模式和Lion风格全屏模式之间切换
  • 监听KeyboardFocusManager以显示当前聚焦组件的层次结构
  • 使用输入映射和KeyListener尝试捕获输入

此外,我还与同事们一起进行了更多的调查,试图找出问题的根源:

首先,我们尝试重写RT.jar中的一些方法,以查看屏幕设备选择的问题。还尝试了Toolkit.beep()功能的入口点,以查看警报声是否来自Java端 - 结果似乎不是。

其次,明显地,甚至本地端也没有接收到键盘事件。一位同事将此归因于7u6中从AWTView切换到NSWindow

已经找到了一些现有的Oracle错误,您可以在这里查找:


更新于10/26:

感谢@maslovalex在下面的评论中提到的适用于7u5的Applet,我回过头来仔细检查了OSX的JDK版本的兼容性:

  • 10.7.1与7u4:全屏工作正常!
  • 10.7.1与7u5:全屏工作正常!
  • 10.7.5与7u5:全屏工作正常!
  • 10.7.5与7u6:全屏功能中断 :(

结合其他地方提到的其他测试,很明显在7u6中引入了一个问题,这个问题在7u7和7u9中仍然存在,并且影响到了Lion 10.7和Mountain Lion 10.8。

7u6是一个重要的里程碑版本,为Mac OS X带来了对JRE和JDK的全面支持,并将Java FX作为分发的一部分。更多信息可以在发布说明路线图中找到。随着支持转向Java FX,出现这样的问题并不令人意外。
问题是:
1. Oracle是否会在近期的JDK发布中修复这个问题?(如果您有现有错误的链接,请在此处包含它们。) 2. 在此期间是否有可能找到解决方法?
今天的其他更新:
我将Apple扩展全屏模式的方法作为一种替代探索路径进行了整合(更新的示例代码待清理)。好消息是:输入正常工作!坏消息是:似乎没有任何的kiosking/isolation选项。 我尝试杀掉Dock - 直接或者通过一个App - 因为我知道Dock负责Command-Tab应用切换、任务控制和启动台,结果发现它也负责处理全屏应用!因此,Java调用变得无效且永远不会返回。 如果有一种方法可以禁用Command-Tab(以及任务控制、启动台和空间),而不影响Dock的全屏处理,那将非常有用。或者,可以尝试重新映射某些键,比如Command键,但这将影响到程序和系统中其他地方使用该修饰键的能力(当你需要Command-C复制一些文本时,这并不理想)。
我在KeyListeners方面没有什么运气(我没有收到任何回调),但我还有几个选项可以尝试。
根据同事的建议,我尝试了通过反射使用((sun.lwawt.macosx.LWCToolkit)Toolkit.getDefaultToolkit()).isApplicationActive()。这个方法的想法是:
这是一个本地方法,注释为“如果应用程序(其中一个窗口)拥有键盘焦点,则返回true”。在过去几个月的CPlatformWindow.java中添加了对该方法的调用,与焦点逻辑有关。如果在您的测试代码中返回false,那可能是问题的一部分。
不幸的是,无论我在哪里检查它,这个方法都返回true。所以即使从低级系统的角度来看,我的窗口应该有键盘焦点。
我之前对JAlbum修复的乐观态度已经破灭。开发人员在他们的论坛上发布了一个回复,解释了他们在运行Java 7时简单地移除了OS X上的正确全屏支持。他们已经向Oracle提交了一个bug(我希望能得到bug编号)。

更新10/25:

我现在也在Lion 10.7.4上尝试了Java 7u9,并且遇到了完全相同的问题,所以这是JDK而不是特定于操作系统。

核心问题是,您是否可以在全屏窗口中嵌入具有默认键盘输入处理的核心Swing组件(JTextField/JTextArea甚至可编辑的组合框),并期望它们正常工作(而无需手动重建它们的基本键绑定)。还有一个问题是,其他窗口布局的支柱,例如使用Tab键进行焦点遍历,是否应该正常工作。

理想的目标是能够将一个带有所有按钮、选项卡、字段等的窗口化Swing应用程序以大部分功能完整地运行在全屏独占/展示模式下。(以前,我发现在Java 6上的OS X上,全屏模式下的对话框弹出或组合框下拉菜单无法正常工作,但其他组件表现正常。)

我将研究eawt的全屏功能,如果它们支持展示模式锁定选项(例如消除Command-Tab应用程序切换),那将非常有趣。


我有一个Swing应用程序,多年来一直支持Mac OS X上的全屏(独占)模式,直到Java 6。我一直在使用最新的Mountain Lion版本(10.8.2补充版)和Oracle的JDK 7进行兼容性测试,并注意到在该模式下存在一个明显的问题:鼠标移动和点击正常工作,但键盘输入无法传递给组件。
(我在下面的测试用例中将此问题缩小到无法在全屏模式下输入简单的JTextField。)
一个症状是每次按键都会导致系统发出蜂鸣声,就好像操作系统不允许将键盘事件传递给应用程序一样。
另外,我的应用程序安装了一个退出钩子,Command-Q组合键将触发该钩子-很明显操作系统正在监听标准键盘组合键。
我已经在三台不同的Mac上进行了单独的测试,安装了不同的版本:
- 在Apple Java 6u35和6u37上:窗口模式和全屏模式都可以接收输入。 - 在Oracle Java 7u7和7u9上:窗口模式按预期工作,而全屏模式出现上述症状。
这可能是之前报道过的: Java图形全屏模式无法注册键盘输入。 然而,该问题对于Java版本或平台并不具体。
进一步搜索发现在Lion中引入了一个单独的全屏选项: OSX Lion上的Java应用全屏功能。 我还没有尝试使用这种方法,因为键盘输入似乎是全屏独占模式的目标使用案例的重要组成部分,比如游戏。
在这种模式的JavaDoc中提到可能会禁用输入方法。我尝试调用建议的Component.enableInputMethods(false),但似乎没有效果
我对这个问题有一些乐观的看法,因为我在一个Java应用程序(JAlbum)的发布说明中找到了一个解决方案。一个10.10.6版本中的修复:“在Mac和Java 7上运行全屏幻灯片时,键盘支持不起作用。”
下面是我的测试案例。它是从这个问题的第二个示例中轻微修改而来(未修改时也存在我的问题):如何在Java中处理全屏独占模式下的键盘和鼠标事件? 特别是,它添加了一个切换全屏的按钮。
import java.lang.reflect.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;

/** @see https://dev59.com/Aeo6XIcBkEYKwwoYPR_f */
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);
            }
        });
    }
}

1
我遇到了完全相同的问题。几周前,我向Oracle发出了错误请求(附带一个简单的Applet代码以进行复现,因为在7u7中我只能使用applets进行复现)-到目前为止还没有答案。你也可以报告它(也许会加快事情的进展)。顺便说一句,7u5可以运行。 - maslovalex
实际上,在jAlbum中,他们在OSX中不使用Java 7的FullScreen(无装饰正确大小的框架)-请检查幻灯片放映。如果我使用Java 6运行它,幻灯片放映会直接进入FS Exclusive。 - maslovalex
1
谢谢您提供的信息@maslovalex。很有趣,7u5可以在您的计算机上工作。我会尝试在我的另一台机器上测试它。 - Vermillion002
@maslovalex,你有提到的那个Oracle bug的链接吗? - Vermillion002
希望在我遇到这个错误时能找到这个。不幸的是,我没有从一个工作程序中出发,所以我认为故障是我的问题,而不是一个错误。这是我9个月前在SO上发布的帖子:http://stackoverflow.com/questions/14317352/bug-java-swing-key-bindings-lose-function-with-jdk-7-in-osx-with-awt-setfullscr/19751521?noredirect=1#19751521,最终我确实报告了它作为一个错误。似乎没有采取任何行动,但知道有一个解决方法还是很好的! - sage88
显示剩余3条评论
3个回答

2
这是因为您添加其他内容的组件现在失去了焦点,您可以通过以下方法进行修复:
  • 在您添加了 KeyBinding 的组件实例上调用 requestFocus()
或者
  • 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);
    

参考资料:


明天早上我会试一下。我认为焦点不是问题的原因之一是当鼠标单击时,JTextField会获得焦点环。此外,您可以使用鼠标选择文本。我的理解是,这些事件将为后续的KeyEvents提供组件焦点,并且JTextField将具有其自己的基本文本输入/标准键入绑定,但显然在全屏模式下默认情况下可能不是这种情况。 - Vermillion002

2

相反,使用键绑定,就像在这个FullScreenTest中所示。同时,考虑使用DocumentListener来处理文本组件,可以参考这里


感谢您的更新。您提到的FullScreenTest(似乎最初是由您贡献的)正是我在上面的示例中使用的起点示例。我的第一次测试涉及直接使用您的FullScreenTest,包括其使用键绑定。不幸的是,它展示了与上述相同的问题,这就是为什么我认为键绑定不是问题的原因。 - Vermillion002
抱歉,我没有10.8版本进行测试。通过setExtendedState()最大化是否是一个替代选择? - trashgod

0

我认为我终于找到了一个解决方案,将点击监听器注册到JFrame本身。(这是一个扩展JFrame类的类,因此所有引用都是"this")。

/**
 * Toggles full screen mode. Requires a lot of references to the JFrame.
 */
public void setFullScreen(boolean fullScreen){
    GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice dev = env.getDefaultScreenDevice();//Gets the main screen
    if(!fullScreen){//Checks if a full screen application isn't open
        this.dispose();//Restarts the JFrame
        this.setVisible(false);
        this.setResizable(true);//Re-enables resize-ability.
        this.setUndecorated(false);//Adds title bar back
        this.setVisible(true);//Shows restarted JFrame
        this.removeMouseListener(macWorkAround);
        this.pack();
        this.setExtendedState(this.getExtendedState()|JFrame.MAXIMIZED_BOTH);//Returns to maximized state
        this.fullScreen = false;
    }
    else{
        this.dispose();//Restarts the JFrame
        this.setResizable(false);//Disables resizing else causes bugs
        this.setUndecorated(true);//removes title bar
        this.setVisible(true);//Makes it visible again
        this.revalidate();
        this.setSize(Toolkit.getDefaultToolkit().getScreenSize());
        try{
            dev.setFullScreenWindow(this);//Makes it full screen
            if(System.getProperty("os.name").indexOf("Mac OS X") >= 0){
                this.setVisible(false);
                this.setVisible(true);
                this.addMouseListener(macWorkAround);
            }
            this.repaint();
            this.revalidate();
        }
        catch(Exception e){
            dev.setFullScreenWindow(null);//Fall back behavior
        }
        this.requestFocus();
        this.fullScreen = true;
    }
}

private MouseAdapter macWorkAround = new MouseAdapter(){
    public void mouseClicked(MouseEvent e){
        MainGUI.this.setVisible(false);
        MainGUI.this.setVisible(true);
    }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接