如何让 Java 输出到我关注的地方?(例如:打开文本文档)

How do I get Java to output to where my focus is? (example: open text document)

提问人:Bernd 提问时间:9/21/2023 更新时间:9/23/2023 访问量:62

问:

我使用 jnativehook.keyboard.NativeKeyListener 来收听在打开的文本文档中键入的键。这里没问题。 经过短暂的排列(想想凯撒密码),我想将输出发送回那个打开的文本文档。 我该怎么做?这似乎出奇地复杂......

java io system.out jnativehook

评论

1赞 Hovercraft Full Of Eels 9/21/2023
实际上,我很惊讶你很惊讶。你不能在没有操作系统的门口就使用标准核心 Java,只是为了监听其他应用程序中的击键,这就是你使用 jnativehook 库的原因。为什么将击键发送到不同的进程会有所不同?现在,如果您的 Java 应用程序启动了该过程,那么使用流可能会更轻松,但除此之外,您可能需要深入研究 JNI 或 JNA 来解决这个问题。
0赞 Hovercraft Full Of Eels 9/21/2023
机器人有时可能会有所帮助,但您需要将流程和正确的流程窗口作为重点,以便可能发挥作用。
0赞 Hovercraft Full Of Eels 9/21/2023
这是Windows系统吗?如果是这样,请查看这个使用 JNA 的答案

答:

-1赞 JayC667 9/21/2023 #1

实际上,与你帖子的评论者所说的相反,有一种 Java 方法可以使用机器人发送关键事件。

  1. Robot bot = new Robot(); (java.awt.Robot)

  2. 如果您有 as in,则可以使用以下方式将该密钥发回NativeKeyEvent pKeyEventorg.jnativehook.keyboard.NativeKeyEvent

         bot.keyPress(pKeyEvent.getRawCode());
         actionSleep(); // sleep for as long as you need, normal is ~20-60 ms
         bot.keyRelease(pKeyEvent.getRawCode());
    
  3. 如果您有 Java Swing/AWT KeyEvent(如 ),则可以像这样使用它:KeyEvent pKeyEventjava.awt.event.KeyEvent

         bot.keyPress(pKeyEvent.getKeyCode());
         actionSleep(); // sleep
         bot.keyRelease(pKeyEvent.getKeyCode());
    
  4. 如果你想通过代码发送密钥,你可以使用它(用于按下和释放密钥“a”):

         bot.keyPress(KeyEvent.VK_A);
         actionSleep(); // sleep
         bot.keyRelease(KeyEvent.VK_A);
    

机器人还具有一些附加功能,例如屏幕捕获/屏幕截图、屏幕像素读取、控制鼠标。

示例应用:

package stackoverflow.keylistenercaesar;

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.github.kwhat.jnativehook.GlobalScreen;
import com.github.kwhat.jnativehook.NativeHookException;
import com.github.kwhat.jnativehook.keyboard.NativeKeyEvent;
import com.github.kwhat.jnativehook.keyboard.NativeKeyListener;

import jc.lib.lang.thread.JcUThread;

/**
 * Written with jnativehook_2.2.0<br>
 * <br>
 * <b>Important!</b> jnativehook_2.1.0 does NOT work correctly under windows, because it does not generate addNativeKeyListener->nativeKeyTyped events!
 *
 * @author jc
 * @since 2023-09-23
 *
 */
public class KeyListenerCaesarChiffre {

    static public final int KEY_PRESS_DURATION_MS = 1;



    static private StringBuilder        sRecordedChars          = new StringBuilder();
    static private ArrayList<Integer>   sRecordedRawKeyCodes    = new ArrayList<>();

    static private Robot sRobot;

    static private volatile boolean sRecordKeys;



    static public void main(final String[] args) throws NativeHookException {
        // disable the stupid logger
        final Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName());
        logger.setLevel(Level.WARNING);
        logger.setUseParentHandlers(false);

        // set up key hook listening
        GlobalScreen.registerNativeHook();
        GlobalScreen.addNativeKeyListener(new NativeKeyListener() {
            @Override public void nativeKeyPressed(final NativeKeyEvent pKeyEvent) {
                //              System.out.println("nativeKeyPressed(c" + pKeyEvent.getKeyCode() + " r" + pKeyEvent.getRawCode() + " c" + pKeyEvent.getKeyChar() + ")");
            }
            @Override public void nativeKeyReleased(final NativeKeyEvent pKeyEvent) {
                //              System.out.println("nativeKeyReleased(c" + pKeyEvent.getKeyCode() + " r" + pKeyEvent.getRawCode() + " c" + pKeyEvent.getKeyChar() + ")");
                try {
                    controlKeyReleased(pKeyEvent);
                } catch (final Exception e) {
                    e.printStackTrace();
                }
            }
            @Override public void nativeKeyTyped(final NativeKeyEvent pKeyEvent) {
                //              System.out.println("nativeKeyTyped(c" + pKeyEvent.getKeyCode() + " r" + pKeyEvent.getRawCode() + " c" + pKeyEvent.getKeyChar() + ")");
                try {
                    inputKeyTyped(pKeyEvent);
                } catch (final Exception e) {
                    e.printStackTrace();
                }
            }
        });

        // print info
        System.out.println("Key Bindings");
        System.out.println("\tKey\tUse");
        System.out.println("\t------\t------");
        System.out.println("\tF2\tStart Listening");
        System.out.println("\tF3\tEnd Listening");
        System.out.println("\tF4\tExit app");
        System.out.println("\tEnter\tTrigger caesar conversion + output");
    }

    /**
     * This method will be called each time any key is released. So we have to filter carefully
     */
    static protected void controlKeyReleased(final NativeKeyEvent pKeyEvent) throws NativeHookException {
        switch (pKeyEvent.getKeyCode()) {
            case NativeKeyEvent.VC_F2: {
                System.out.println("Start listening...");
                sRecordKeys = true;
                break;
            }
            case NativeKeyEvent.VC_F3: {
                System.out.println("Stop listening...");
                sRecordKeys = false;
                break;
            }
            case NativeKeyEvent.VC_F4: {
                System.out.println("Shutting down...");
                GlobalScreen.unregisterNativeHook();
                break;
            }
            case NativeKeyEvent.VC_ENTER: {
                if (sRecordKeys) runCasesarAndOutput();
                break;
            }
            default: // ignore the rest
        }
    }

    /**
     * This method will only get triggered for valid input chars. So no control codes like F1, F2 etc will arrive here
     */
    static protected void inputKeyTyped(final NativeKeyEvent pKeyEvent) {
        if (!sRecordKeys) return;

        //      System.out.println("KeyListenerCaesarChiffre.inputKeyTypedx(" + pKeyEvent.getKeyCode() + " r" + pKeyEvent.getRawCode() + " c" + pKeyEvent.getKeyChar() + ")");
        switch (pKeyEvent.getKeyChar()) {
            default: {
                //              System.out.println("Key:\t" + pKeyEvent.getKeyCode() + " / " + pKeyEvent.getRawCode() + " => " + pKeyEvent.getKeyChar());
                sRecordedChars.append(pKeyEvent.getKeyChar());
                sRecordedRawKeyCodes.add(Integer.valueOf(pKeyEvent.getRawCode()));
                System.out.println("\tlen:" + sRecordedChars.length() + "\ttext:" + sRecordedChars.toString());
            }
        }
    }



    /**
     * This will need to run in its own thread.
     * If we do not, we would run in the key event listener thread, and all keys simulated here would only be processed AFTER this method is done,
     * so it would record its own inputs.
     */
    static private void runCasesarAndOutput() {
        new Thread(() -> {
            try {
                runCasesarAndOutput_();
            } catch (final AWTException e) {
                e.printStackTrace();
            }
        }).start();
    }
    static private void runCasesarAndOutput_() throws AWTException {
        // wait until enter key event is processed
        sleep(50);

        try {
            // suspend key listening temporaily or we will feed back into our own input
            sRecordKeys = false;

            // send output. Do NOT process the enter key, thus size-1
            for (int i = 0; i < sRecordedRawKeyCodes.size() - 1; i++) {
                final int c = sRecordedRawKeyCodes.get(i).intValue();
                typeKey(c + 1); // this is a really bad implementation of caesar
                // especially because this will fall out of the keymapping boundaries a lot!
                // some normal chars might get converted into control chars, like the arrow keys, for example
                // also, letters do not wrap back up (z does not wrap back up to a in case of +1)
                // also, you need to take additional care of special control keys like backspace the handle the ensuing "text" properly!
            }

            // send enter to finish line
            typeKey(KeyEvent.VK_ENTER);

        } finally {
            sRecordKeys = true;
        }

        // clear intercepted key chars
        sRecordedChars.setLength(0);
        sRecordedRawKeyCodes.clear();
    }



    static public void typeKey(final int pKeyCode) throws AWTException {
        if (sRobot == null) sRobot = new Robot();
        try {
            sRobot.keyPress(pKeyCode);
            sleep(KEY_PRESS_DURATION_MS);
            sRobot.keyRelease(pKeyCode);
            //          sleep(KEY_PRESS_DURATION_MS);

        } catch (final java.lang.IllegalArgumentException e) {
            System.err.println("Key code [" + pKeyCode + "] is invalid!");
            e.printStackTrace();
        }
    }
    static public void beep(final int pCount) {
        final Toolkit dtk = Toolkit.getDefaultToolkit();
        for (int i = 0; i < pCount; i++) {
            dtk.beep();
            JcUThread.sleep(300);
        }
    }
    static public void sleep(final int pMS) {
        try {
            Thread.sleep(pMS);
        } catch (final InterruptedException e) { /* */ }
    }



}

这种实现有很多缺点和问题本身:

  • 需要 jnativehook_2.2.0,因为早期版本在 Windows 下无法可靠地工作
  • 在处理控制键(如退格键、箭头键等)时需要特别小心。
  • 凯撒削片机将是一个痛苦的屁股,无法正确实施
  • 评论中的更多详细信息

用法:

  • 运行应用
  • 进入您的文本编辑器
  • 按 F2 开始录制按键
  • 当您按 Enter 键时,按键将被凯撒转换,然后作为击键发送到文档
  • 按 F3 停止录制
  • 或按 F4 关闭应用

评论

1赞 Hovercraft Full Of Eels 9/21/2023
我已经提到过机器人。不过,问题在于将注意力集中在正确的独立流程上,这是您的答案无法解决的问题,除非我遗漏了什么。
0赞 JayC667 9/21/2023
是的,但你暗示了重点话题,即使没有提到它,似乎也不是必需的。这篇文章提到了在后台运行的 Java 应用程序,在前台监听文本编辑的文本/击键。然后通过凯撒密码进行处理,这也可以在后台完成。所以对我来说,听起来没有必要“更改活动应用程序”,因为该文本编辑器仍然具有焦点。否则,要使用 Alt+Tab 和 stackoverflow.com/questions/34992027/...
0赞 Bernd 9/22/2023
我以为我会简单地反转 NativeKeyListener,因为它已经知道哪个窗口处于焦点:System.out.FocusOwner(...) 或类似窗口。
0赞 Bernd 9/22/2023
澄清:是的,我不打算改变打开文本文档的重点,但它可能会发生(随机电子邮件等)。谢谢,我将研究机器人,然后是 JNI/JNA。为了方便起见,我使用 Windows,但我打算在 MacOS 上使用最终应用程序。
0赞 Bernd 9/23/2023
@JayC667,我尝试了机器人,但根本没有输出。你能写一些以“public void nativeKeyTyped(NativeKeyEvent pKeyEvent)”开头的演示代码吗?