Компоненты FullScreen Swing не могут получить ввод с клавиатуры на Java 7 в Mac OS X Mountain Lion

Обновление 12/21:

7u10 был недавно выпущен. Подтвердили, что:

  1. Проблема все еще сохраняется
  2. К счастью, обходной путь все еще функционирует!

Обновление 11/7:

И у нас есть обходной путь!

Леонид Романов из Oracle в списке рассылки openjdk.java.net дал некоторое представление о том, что происходит:

Хорошо, хотя я еще не уверен на 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, которые вы можете посмотреть здесь:


Обновление 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.

Вопрос становится:

  1. Сможет ли Oracle это исправить в ближайшем выпуске JDK? (Если у вас есть ссылки на существующие ошибки, пожалуйста, включите их здесь.)
  2. Возможен ли временный обходной путь?

Другие обновления с сегодняшнего дня:

  • Я включил подход расширений 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() на экземпляре компонента, к которому вы добавляете KeyBindings

или же

  • альтернативно использовать JComponent.WHEN_IN_FOCUSED_WINDOW с KeyBindings:

    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);
    }
};
Другие вопросы по тегам