12/21 更新:
7u10 最近发布了。确认:
- 问题依然存在
- 值得庆幸的是,解决方法仍然有效!
11/7 更新:
我们有一个解决方法!
来自 Oracle 在 openjdk.java.net 邮件列表上的 Leonid Romanov 提供了一些关于正在发生的事情的见解:
好吧,虽然我还不是 100% 确定,但看起来当我们进入全屏时,其他窗口成为第一响应者,因此发出哔哔声。您能否尝试以下解决方法:在框架上调用 setFullScreenWindow() 后,调用 setVisible(false),然后调用 setVisible(true)。从理论上讲,这应该恢复正确的第一响应者。
似乎有效的代码片段很简单:
dev.setFullScreenWindow(f);
f.setVisible(false);
f.setVisible(true);
我已经更新了示例代码,能够打开和关闭此修复;每次窗口进入全屏时都需要它。
在我更复杂的应用程序的更大上下文中,我仍然在全屏窗口中的子组件上遇到键盘焦点问题,其中鼠标单击会导致我的窗口失去焦点。(我猜它会转到上面提到的不受欢迎的第一响应者窗口。)当我有更多关于这个案例的信息时,我会报告——我还不能在较小的样本中重现它。
10/31 更新:
示例代码的主要更新:
- 包括在 FullScreen 独有模式和 Lion 风格的 FullScreen 模式之间切换
- 监听以
KeyboardFocusManager
显示当前焦点组件的层次结构 - 使用输入映射和
KeyListener
s 来尝试捕获输入
还与同事进行了更多挖掘以尝试隔离问题:
一方面,我们尝试覆盖 RT.jar 中的一些方法,以查看屏幕设备的选择方式是否存在问题。还尝试了 Toolkit.beep() 功能的入口点,以查看警报声音是否来自 Java 端 - 似乎不是。
另一方面,很明显,即使是本地端也没有接收到键盘事件。一位同事将此归因于 7u6 中从 aAWTView
到 a的转换。NSWindow
已经找到了一些现有的 Oracle 错误,您可以在此处查找:
- 8000276:[macosx] graphicsDevice.setFullScreenWindow(frame) 使 JVM 崩溃
- 8000430:[macosx] macOS 上的 java.awt.FileDialog 问题
- 7175707 : [macosx] PIT: 8 b43 未在 AppKit 线程问题上再次运行
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 的支持转移,这样的问题可能会突然出现,这并不奇怪。
问题变成:
- Oracle 会在 JDK 的近期版本中解决这个问题吗?(如果您有指向现有错误的链接,请在此处包含它们。)
- 在此期间是否可以解决问题?
今天的其他更新:
我将Apple 扩展方法合并到全屏模式作为探索的替代路径(更新的示例代码等待清理)。好消息:输入有效!坏消息:似乎真的没有任何信息亭/隔离选项。 我尝试直接或使用应用程序
杀死 Dock ,因为我了解 Dock 负责 Command-Tab 应用程序切换、任务控制和 Launch Pad,却发现它也负责处理全屏应用程序!因此,Java 调用变得无效并且永远不会返回。 如果有办法禁用 Command-Tab
(以及 Mission Control 和 Launchpad 和 Spaces)而不影响 Dock 的全屏处理,这将非常有用。或者,可以尝试重新映射某些键,例如 Command,但这会影响在程序和系统本身的其他地方使用该修饰符的能力(当您需要使用 Command-C 复制某些文本时,这并不理想)。我对 KeyListeners 没有运气(我没有收到任何回调),但我还有更多选择可以尝试。
根据同事的建议,我
((sun.lwawt.macosx.LWCToolkit)Toolkit.getDefaultToolkit()).isApplicationActive()
通过反思进行了尝试。这个想法是:它
是一个带有注释的本地方法“如果应用程序(它的一个窗口)拥有键盘焦点,则返回true。”。过去几个月,CPlatformWindow.java 中添加了对该方法的调用,与焦点逻辑相关。如果它在您的测试代码中返回 false,则可能是问题的一部分。
不幸的是,无论我在哪里检查它,该方法都返回 true。所以即使根据低级系统,我的窗口也应该有键盘焦点。我之前对 JAlbum 修复的乐观态度已经破灭。开发人员在他们的论坛上发布了一个回复,解释了他们如何在运行 Java 7 时简单地删除了 OS X 上的正确全屏支持。他们在 Oracle 中有一个错误(我希望得到错误编号)。
10/25 更新:
我现在也在 Lion 10.7.4 上尝试了 Java 7u9,并且看到了完全相同的问题,所以它是 JDK - 而不是特定于操作系统的。
核心问题已成为您是否可以在全屏窗口中嵌入对键盘输入(JTextField/JTextArea
甚至可编辑的组合框)具有默认处理的核心 Swing 组件,并期望它们正常运行(无需求助于手动重建其基本键绑定)。还有一个问题是其他窗口布局的忠实拥护者,例如使用选项卡进行焦点遍历,是否应该工作。
理想的目标是能够使用带有所有按钮、选项卡、字段等的窗口化 Swing 应用程序,并以全屏独占/信息亭模式运行它,并且大部分功能完好无损。(以前,我看到对话框弹出窗口或 ComboBox 下拉菜单在 OS X 上的 Java 6 上无法全屏运行,但其他组件表现良好。)
我将研究 eawt FullScreen 功能,如果它们支持 kiosk 锁定选项(例如消除 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 Graphics Full Screen Mode not Registering Keyboard Input。但是,该问题并不特定于 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://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);
}
});
}
}