令人困惑的按键操作案例
Case of the confounding key press caper
背景
开发用于屏幕投射的基本开源键盘和鼠标屏幕显示桌面应用程序,名为 KmCaster:
应用程序使用 JNativeHook library to receive global keyboard and mouse events, because Swing's Key and Mouse 侦听器仅限于接收针对应用程序本身的事件。
问题
当应用程序失去焦点时,用户界面会显示断断续续的按键操作,而不是每次按键操作。然而控制台显示应用程序已收到每个按键。
代码
一个简短、独立、可编译的示例:
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.jnativehook.keyboard.NativeKeyEvent;
import org.jnativehook.keyboard.NativeKeyListener;
import javax.swing.*;
import static java.util.logging.Level.OFF;
import static java.util.logging.Logger.getLogger;
import static javax.swing.SwingUtilities.invokeLater;
import static org.jnativehook.GlobalScreen.*;
import static org.jnativehook.keyboard.NativeKeyEvent.getKeyText;
public class Harness extends JFrame implements NativeKeyListener {
private final JLabel mLabel = new JLabel( "Hello, world" );
private int mCount;
public void init() {
getContentPane().add( mLabel );
setDefaultCloseOperation( EXIT_ON_CLOSE );
setLocationRelativeTo( null );
setAlwaysOnTop( true );
pack();
setVisible( true );
}
@Override
public void nativeKeyPressed( final NativeKeyEvent e ) {
final var s = getKeyText( e.getKeyCode() );
System.out.print( s + " " + (++mCount % 10 == 0 ? "\n" : "") );
invokeLater( () -> mLabel.setText( s ) );
}
public static void main( final String[] args ) throws NativeHookException {
disableNativeHookLogger();
registerNativeHook();
final var harness = new Harness();
addNativeKeyListener( harness );
invokeLater( harness::init );
}
private static void disableNativeHookLogger() {
final var logger = getLogger( GlobalScreen.class.getPackage().getName() );
logger.setLevel( OFF );
logger.setUseParentHandlers( false );
}
@Override
public void nativeKeyReleased( final NativeKeyEvent e ) {}
@Override
public void nativeKeyTyped( final NativeKeyEvent e ) {}
}
上面的代码产生了一个小的 window,当 运行 时,演示了问题:
请务必输入任何其他 window 以查看演示应用程序中令人困惑的按键丢失。
环境
- OpenJDK 版本“14.0.1”2020-04-14,64 位
- XFCE
- 拱门Linux
- JNativeHook 2.1.0
详情
JNativeHook 运行s 在其自己的线程中,但使用 invokeLater
(或 invokeAndWait
?)应该在 Swing 的事件线程上发布 UI 更新。
对 disableNativeHookLogger()
的调用无关紧要,它只是在 运行 进行演示时保持控制台清洁。
控制台输出
这是应用程序获得焦点时的控制台输出:
Shift I Space A M Space I N S I
D E Space T H E Space A P P
L I C A T I O N Period
这是应用程序失去焦点时的控制台输出:
Shift I Space A M Space O U T S
I D E Space T H E Space A P
P L I C A T I O N Period
很明显,调用 nativeKeyPressed
时,无论应用程序是否具有焦点,都不会丢失任何键盘事件。也就是说,JNativeHook 及其通过 JNI 冒泡的事件似乎都不是罪魁祸首。
问题
需要更改什么才能使 JLabel
文本在每次按键时更新,而不管应用程序是否具有焦点?
想法
一些有帮助的项目包括:
- 调用
getDefaultToolkit().sync();
显式刷新渲染管道。
- 在标签上调用
paintImmediately( getBounds() )
。
第一项似乎有很大的不同,但有些键似乎仍然丢失(尽管可能是我打字太快了)。阻止渲染管线合并绘画请求可以避免击键丢失是有道理的。
研究
与此问题相关的资源:
使用默认工具包调用 sync()
:
@Override
public void propertyChange( final PropertyChangeEvent e ) {
invokeLater(
() -> {
update( e );
// Prevent collapsing multiple paint events.
getDefaultToolkit().sync();
}
);
}
参见full code。
背景
开发用于屏幕投射的基本开源键盘和鼠标屏幕显示桌面应用程序,名为 KmCaster:
应用程序使用 JNativeHook library to receive global keyboard and mouse events, because Swing's Key and Mouse 侦听器仅限于接收针对应用程序本身的事件。
问题
当应用程序失去焦点时,用户界面会显示断断续续的按键操作,而不是每次按键操作。然而控制台显示应用程序已收到每个按键。
代码
一个简短、独立、可编译的示例:
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.jnativehook.keyboard.NativeKeyEvent;
import org.jnativehook.keyboard.NativeKeyListener;
import javax.swing.*;
import static java.util.logging.Level.OFF;
import static java.util.logging.Logger.getLogger;
import static javax.swing.SwingUtilities.invokeLater;
import static org.jnativehook.GlobalScreen.*;
import static org.jnativehook.keyboard.NativeKeyEvent.getKeyText;
public class Harness extends JFrame implements NativeKeyListener {
private final JLabel mLabel = new JLabel( "Hello, world" );
private int mCount;
public void init() {
getContentPane().add( mLabel );
setDefaultCloseOperation( EXIT_ON_CLOSE );
setLocationRelativeTo( null );
setAlwaysOnTop( true );
pack();
setVisible( true );
}
@Override
public void nativeKeyPressed( final NativeKeyEvent e ) {
final var s = getKeyText( e.getKeyCode() );
System.out.print( s + " " + (++mCount % 10 == 0 ? "\n" : "") );
invokeLater( () -> mLabel.setText( s ) );
}
public static void main( final String[] args ) throws NativeHookException {
disableNativeHookLogger();
registerNativeHook();
final var harness = new Harness();
addNativeKeyListener( harness );
invokeLater( harness::init );
}
private static void disableNativeHookLogger() {
final var logger = getLogger( GlobalScreen.class.getPackage().getName() );
logger.setLevel( OFF );
logger.setUseParentHandlers( false );
}
@Override
public void nativeKeyReleased( final NativeKeyEvent e ) {}
@Override
public void nativeKeyTyped( final NativeKeyEvent e ) {}
}
上面的代码产生了一个小的 window,当 运行 时,演示了问题:
请务必输入任何其他 window 以查看演示应用程序中令人困惑的按键丢失。
环境
- OpenJDK 版本“14.0.1”2020-04-14,64 位
- XFCE
- 拱门Linux
- JNativeHook 2.1.0
详情
JNativeHook 运行s 在其自己的线程中,但使用 invokeLater
(或 invokeAndWait
?)应该在 Swing 的事件线程上发布 UI 更新。
对 disableNativeHookLogger()
的调用无关紧要,它只是在 运行 进行演示时保持控制台清洁。
控制台输出
这是应用程序获得焦点时的控制台输出:
Shift I Space A M Space I N S I
D E Space T H E Space A P P
L I C A T I O N Period
这是应用程序失去焦点时的控制台输出:
Shift I Space A M Space O U T S
I D E Space T H E Space A P
P L I C A T I O N Period
很明显,调用 nativeKeyPressed
时,无论应用程序是否具有焦点,都不会丢失任何键盘事件。也就是说,JNativeHook 及其通过 JNI 冒泡的事件似乎都不是罪魁祸首。
问题
需要更改什么才能使 JLabel
文本在每次按键时更新,而不管应用程序是否具有焦点?
想法
一些有帮助的项目包括:
- 调用
getDefaultToolkit().sync();
显式刷新渲染管道。 - 在标签上调用
paintImmediately( getBounds() )
。
第一项似乎有很大的不同,但有些键似乎仍然丢失(尽管可能是我打字太快了)。阻止渲染管线合并绘画请求可以避免击键丢失是有道理的。
研究
与此问题相关的资源:
使用默认工具包调用 sync()
:
@Override
public void propertyChange( final PropertyChangeEvent e ) {
invokeLater(
() -> {
update( e );
// Prevent collapsing multiple paint events.
getDefaultToolkit().sync();
}
);
}
参见full code。