【问题标题】:Case of the confounding key press caper混淆按键跳跃案例
【发布时间】:2020-11-16 06:57:18
【问题描述】:

背景

开发用于屏幕投射的基本开源键盘和鼠标屏幕显示桌面应用程序,称为KmCaster

应用程序使用 JNativeHook 库来接收全局键盘和鼠标事件,因为 Swing 的 KeyMouse 侦听器仅限于接收指向应用程序本身的事件。

问题

当应用程序失去焦点时,用户界面会显示间歇性按键,而不是每次按键。然而控制台显示应用程序已收到每一次按键。

代码

一个简短的、独立的、可编译的示例:

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 ) {}
}

上面的代码会生成一个小窗口,运行时会演示问题:

请务必在任何其他窗口中输入,以查看演示应用程序中令人费解的按键丢失情况。

环境

  • OpenJDK 版本“14.0.1”2020-04-14,64 位
  • XFCE
  • Arch Linux
  • JNativeHook 2.1.0

详情

JNativeHook 在自己的线程中运行,但使用 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() )

第一项似乎有很大的不同,但似乎仍然缺少一些键(尽管可能是我打字太快了)。阻止渲染管道合并绘制请求可以避免键击丢失是有道理的。

研究

与此问题相关的资源:

【问题讨论】:

  • 虽然可能是我打字太快了 - 如果您使用 JTextArea 并在收到每个字符串时追加它会发生什么? (或使用带有label.settext的jlabel.settext(label.gettext(...)+ s)您是否看到所有文本或实际丢失的字符?在您的示例中,您始终设置文本,以便您只看到最后一次字符,所以是的,如果可能是合并绘画请求的结果。我没有看到解决方案。
  • JTextArea/append 会改变测试的性质,Heisenbug 风格吗? (我尝试了所有的角色都会出现,因为它们在控制台中显示出来并不令人惊讶。)Pavel的帖子最后描述了987654340 sync() 987654341 @呼叫@ 987654341;只有当最终结果等同于仅发出最终的paint() 请求时,才能折叠这些绘制调用——附加文本不满足的条件。
  • 问题在于,即使您单独强制重绘,绘画也可能发生得如此之快,以至于您看不到两种结果。您可以尝试调用mLabel.paintImmediately(...),这将在不向 RepaintManager 添加请求的情况下绘制组件。

标签: java swing desktop-application jnativehook


【解决方案1】:

使用默认工具包调用sync()

  @Override
  public void propertyChange( final PropertyChangeEvent e ) {
    invokeLater(
        () -> {
          update( e );

          // Prevent collapsing multiple paint events.
          getDefaultToolkit().sync();
        }
    );
  }

请参阅full code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-28
    • 2020-02-11
    • 1970-01-01
    相关资源
    最近更新 更多