-1

I used jnativehook.keyboard.NativeKeyListener to listen to keys typed in an open text document. No problem here. After a short permutation (think Caesar cipher), I want to send the output back to that open text document. How do I do this? This seems to be surprisingly complicated...

3
  • 1
    Actually, I'm surprised that you're surprised. You can't use standard core Java without some doorway to the operating system just to listen to key strokes in other applications, which is why you are using the jnativehook library. Why should sending keystrokes to a different process be any different? Now if your Java application started the process, then you'll have an easier time by perhaps using streams, but otherwise, you'll likely need to dive into JNI or JNA to solve this issue. Commented Sep 20, 2023 at 23:19
  • The robot may sometimes be able to help, but you need to give the process and the correct process window the focus for this to possibly work. Commented Sep 20, 2023 at 23:19
  • Is this a Windows system? If so, please look at this answer which uses JNA. Commented Sep 20, 2023 at 23:25

1 Answer 1

-1

Actually, opposed to what the commenters to your post say, there IS a Java way to send key events, using the Robot.

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

  2. If you have NativeKeyEvent pKeyEvent as in org.jnativehook.keyboard.NativeKeyEvent then you can send that key back with

         bot.keyPress(pKeyEvent.getRawCode());
         actionSleep(); // sleep for as long as you need, normal is ~20-60 ms
         bot.keyRelease(pKeyEvent.getRawCode());
    
  3. If you have the Java Swing/AWT KeyEvent (KeyEvent pKeyEvent as in java.awt.event.KeyEvent), you can use it like this:

         bot.keyPress(pKeyEvent.getKeyCode());
         actionSleep(); // sleep
         bot.keyRelease(pKeyEvent.getKeyCode());
    
  4. If you want to send keys via code, you can use this (for pressing and releasing key 'a'):

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

The Robot also has some additional functionality, like screen capture / screenshot, screen pixel reading, control mouse.

Example app:

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



}

There's lots of downsides to this implementation and the problem per se:

  • Needs jnativehook_2.2.0 as earlier versions do NOT work reliably under Windows
  • Needs a lot of special care when handling control keys like backspace, arrows etc.
  • Caesar chiper will be a pain in the ass to implement properly
  • More details in the comments

Usage:

  • Run the app
  • Head into your text editor
  • Press F2 to start recording the keys
  • When you press Enter, the keys will be caesar-transformed and then sent to the doc as keystrokes
  • Press F3 to halt recording
  • Or press F4 to shut down the app
Sign up to request clarification or add additional context in comments.

8 Comments

I mentioned robot already. The issue though is getting focus to the right separate process, something that your answer doesn't address, unless I'm missing something.
Yeah but you implied that topic of the focus, even though it was not mentioned and does not seem required. The post mentions the Java app running in the background, listening to the text/keystrokes of the text edit in the foreground. Then processing it via Caesar cipher, which also can be done in the background. So to me it sounds like there's no 'changing of active app' necessary, because that text editor still has the focus. Otherwise, to so solve with Alt+Tab and stackoverflow.com/questions/34992027/…
I thought I would simply reverse the NativeKeyListener, as it already knows what window is in focus: System.out.FocusOwner(...) or similar.
Clarification: Yes, I do not intend to change the focus of the open text document, but it may happen (random email coming in etc.). Thanks, I will look into robot and then JNI/JNA. I'm using Windows for convenience but I intend to use the final App on MacOS.
@JayC667, I tried the Robot but there is no output at all. Could you write some demo code starting with "public void nativeKeyTyped(NativeKeyEvent pKeyEvent)"?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.