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...
-
1Actually, 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.Hovercraft Full Of Eels– Hovercraft Full Of Eels2023-09-20 23:19:00 +00:00Commented 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.Hovercraft Full Of Eels– Hovercraft Full Of Eels2023-09-20 23:19:44 +00:00Commented Sep 20, 2023 at 23:19
-
Is this a Windows system? If so, please look at this answer which uses JNA.Hovercraft Full Of Eels– Hovercraft Full Of Eels2023-09-20 23:25:30 +00:00Commented Sep 20, 2023 at 23:25
1 Answer
Actually, opposed to what the commenters to your post say, there IS a Java way to send key events, using the Robot.
Robot bot = new Robot();(java.awt.Robot)If you have
NativeKeyEvent pKeyEventas inorg.jnativehook.keyboard.NativeKeyEventthen you can send that key back withbot.keyPress(pKeyEvent.getRawCode()); actionSleep(); // sleep for as long as you need, normal is ~20-60 ms bot.keyRelease(pKeyEvent.getRawCode());If you have the Java Swing/AWT KeyEvent (
KeyEvent pKeyEventas injava.awt.event.KeyEvent), you can use it like this:bot.keyPress(pKeyEvent.getKeyCode()); actionSleep(); // sleep bot.keyRelease(pKeyEvent.getKeyCode());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