Компоненты FullScreen Swing не могут получить ввод с клавиатуры на Java 7 в Mac OS X Mountain Lion
Обновление 12/21:
7u10 был недавно выпущен. Подтвердили, что:
- Проблема все еще сохраняется
- К счастью, обходной путь все еще функционирует!
Обновление 11/7:
И у нас есть обходной путь!
Хорошо, хотя я еще не уверен на 100%, но похоже, что когда мы входим в полноэкранный режим, какое-то другое окно становится первым респондентом, следовательно, звуковым сигналом. Не могли бы вы попробовать следующий обходной путь: после вызова setFullScreenWindow() для фрейма вызовите setVisible(false), а затем setVisible (true). Это, по идее, должно восстановить правильного первого респондента.
Фрагмент кода, который, кажется, работает:
dev.setFullScreenWindow(f);
f.setVisible(false);
f.setVisible(true);
Я обновил пример кода с возможностью включать и выключать это исправление; это требуется каждый раз, когда окно входит в полноэкранный режим.
В более широком контексте моего более сложного приложения я все еще сталкиваюсь с проблемами фокусировки клавиатуры на подкомпонентах в полноэкранном окне, где щелчок мыши приводит к тому, что мое окно теряет фокус. (Я предполагаю, что это происходит в нежелательном окне первого респондента, указанном выше.) Я сообщу, когда у меня будет больше информации об этом случае - я пока не могу воспроизвести его в меньшем примере.
Обновление 10/31:
Основное обновление примера кода:
- Включает в себя переключение между эксклюзивным режимом FullScreen и режимом FullScreen в стиле Lion
- Слушает
KeyboardFocusManager
отобразить иерархию для текущего сфокусированного компонента - Использует обе входные карты и
KeyListener
s, чтобы попытаться захватить ввод
Также сделал еще несколько копаний с коллегами, чтобы попытаться изолировать проблемы:
С одной стороны, мы попытались переопределить некоторые методы в RT.jar, чтобы увидеть, были ли проблемы с выбором устройства экрана. Также были опробованы точки входа в функциональность Toolkit.beep(), чтобы увидеть, поступают ли звуки оповещения со стороны Java - не отображается.
С другой стороны, было ясно, что даже нативная сторона не получает события клавиатуры. Коллега приписывает это переключению с AWTView
к NSWindow
в 7u6.
Был найден набор существующих ошибок Oracle, которые вы можете посмотреть здесь:
- 8000276: [macosx] graphicsDevice.setFullScreenWindow (frame) приводит к сбою JVM
- 8000430: [macosx] java.awt.FileDialog проблемы на macosx
- 7175707: [macosx] PIT: 8 b43 Не работает снова проблема с потоком AppKit
Обновление 10/26:
Благодаря комментарию @maslovalex ниже относительно апплета, работающего на 7u5, я вернулся и тщательно изучил совместимость с версиями JDK для OSX:
- 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 был важной вехой версией, обеспечивающей полную поддержку JRE и JDK для Mac OS X, а также включающей Java FX в качестве части дистрибутива. Дополнительная информация доступна в примечаниях к выпуску и дорожной карте. Не удивительно, что такая проблема может возникнуть по мере перехода поддержки на Java FX.
Вопрос становится:
- Сможет ли Oracle это исправить в ближайшем выпуске JDK? (Если у вас есть ссылки на существующие ошибки, пожалуйста, включите их здесь.)
- Возможен ли временный обходной путь?
Другие обновления с сегодняшнего дня:
Я включил подход расширений Apple к полноэкранному режиму в качестве альтернативного пути исследования (обновленный пример кода в ожидании очистки). Хорошая новость: вход работает! Плохая новость: на самом деле, похоже, нет вариантов киосков / изоляции.
Я попытался убить Dock - напрямую или с помощью приложения - поскольку я понимаю, что Dock отвечает за переключение приложений Command-Tab, Mission Control и 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 был разбит. Разработчик разместил ответ на своем форуме, который объясняет, как они просто удалили надлежащую полноэкранную поддержку в OS X при запуске Java 7. У них есть ошибка в Oracle (и я надеюсь получить номер ошибки).
Обновление 10/25:
Я также попробовал Java 7u9 на Lion 10.7.4 и увидел точно такую же проблему, так что это JDK- не зависит от ОС.
Основной вопрос заключается в том, можете ли вы встраивать в полноэкранное окно базовые компоненты Swing, которые по умолчанию обрабатывают ввод с клавиатуры (JTextField/JTextArea
или даже редактируемые поля со списком) и ожидайте, что они будут вести себя нормально (не прибегая к перестройке основных привязок клавиш вручную). Также вопрос в том, должны ли работать другие сторонники оконных макетов, такие как использование табуляции для обхода фокуса.
Идеальной целью было бы иметь возможность взять оконное приложение Swing со всеми его кнопками, вкладками, полями и т. Д. И запустить его в полноэкранном монопольном режиме / режиме киоска с сохранением большей части функциональности. (Ранее я видел, что всплывающие окна Dialog или выпадающие списки ComboBox не работают в полноэкранном режиме на Java 6 в OS X, но другие компоненты работают нормально.)
Я рассмотрю возможности eawt FullScreen, которые будут интересны, если они будут поддерживать опции блокировки киоска, такие как устранение переключения приложений Command-Tab.
Оригинальный вопрос:
У меня есть приложение Swing, которое годами поддерживало режим FullScreen (эксклюзивный) на Mac OS X вплоть до Java 6. Я проводил тестирование совместимости с последней версией Mountain Lion (10.8.2 Supplemental) и Oracle JDK 7 и заметил вопиющая проблема в этом режиме: движение мыши и щелчки работают нормально, но ввод с клавиатуры не доставляется компонентам.
(Я сузил это в тестовом примере ниже, чтобы не было возможности набирать текст в простом JTextField в полноэкранном режиме.)
Один из симптомов состоит в том, что каждое нажатие клавиши приводит к системному звуковому сигналу, как будто ОС запрещает передачу событий клавиатуры в приложение.
Отдельно в моем приложении установлен выходной хук, и комбо Command-Q вызовет этот хук - ясно, что ОС слушает стандартные комбинации клавиш.
Я проверил это отдельно на трех разных Mac с различными установками:
- На Apple Java 6u35 и 6u37: оконный и полноэкранный режимы получают входные данные.
- В Oracle Java 7u7 и 7u9: оконный режим работает, как и ожидалось, в то время как у полноэкранного режима есть признаки, описанные выше.
Возможно, ранее сообщалось: Полноэкранный режим Java Graphics не регистрирует ввод с клавиатуры. Однако этот вопрос не является специфическим для версии или платформы Java.
При дополнительном поиске появилась отдельная полноэкранная опция, представленная в Lion: полноэкранная функция для приложений Java на OSX Lion. Я еще не попробовал использовать этот подход, поскольку ввод с клавиатуры кажется неотъемлемой частью целевых сценариев использования режима FullScreen Exclusive, таких как игры.
В 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://stackru.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);
}
});
}
}
3 ответа
Это потому, что компонент, к которому вы добавили другой, теперь потерял фокус, это можно исправить одним из следующих способов:
- призвание
requestFocus()
на экземпляре компонента, к которому вы добавляетеKeyBinding
s
или же
альтернативно использовать
JComponent.WHEN_IN_FOCUSED_WINDOW
сKeyBinding
s:component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, 0), "doSomething"); component.getActionMap().put("doSomething", anAction);
Ссылка:
Вместо этого используйте привязки клавиш, как показано в этом FullScreenTest
, Кроме того, рассмотрим DocumentListener
показано здесь, для текстовых компонентов.
Я думаю, что наконец-то нашел решение, регистрируя прослушиватели кликов по отношению к самому 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);
}
};