package com.mattc.autotyper.robot;
import static java.awt.event.KeyEvent.*;
import com.mattc.autotyper.Autotyper;
import com.mattc.autotyper.Parameters;
import com.mattc.autotyper.meta.FXCompatible;
import com.mattc.autotyper.meta.SwingCompatible;
import com.mattc.autotyper.util.Console;
import org.jnativehook.keyboard.NativeKeyListener;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
* Mimics the Keyboard to a large extent. <br />
* <br />
* Makes extensive use of {@link Robot}, JNativeHooks and switching between
* KeyboardMode's to mimic user input after being given a character, String or File.
* To make use of JNativeHooks, Keyboard does implement {@link NativeKeyListener} but
* must be manually registered. <br />
* <br />
* Making use of {@link KeyboardMode}, this Keyboard can exist in 1 of 3 states:
* ACTIVE, PAUSED, or INACTIVE. ACTIVE means the keyboard is currently typing.
* INACTIVE means the keyboard is not typing nor in the middle of a typing action,
* and PAUSED means the Keyboard is not typing, but IS in the middle of a session.
*
* @author Matthew
*/
@FXCompatible
@SwingCompatible
class SwingKeyboard extends Keyboard {
private final Robot robo;
private Methodology method;
/**
* Create an instance of Keyboard with the given delay between Key Presses.
*
* @param actionDelay
*/
SwingKeyboard(int actionDelay) {
try {
actionDelay = Math.max(actionDelay, Parameters.MIN_DELAY);
this.robo = new Robot();
this.robo.setAutoDelay(actionDelay);
this.robo.setAutoWaitForIdle(true);
} catch (final AWTException e) {
throw new IllegalStateException("Could not create java.awt.Robot!", e);
}
}
/**
* Type a Single Character
*
* @param c
*/
@Override
public void type(char c) {
// A bit verbose, but necessary.
switch (c) {
case 'a':
doType(VK_A);
break;
case 'b':
doType(VK_B);
break;
case 'c':
doType(VK_C);
break;
case 'd':
doType(VK_D);
break;
case 'e':
doType(VK_E);
break;
case 'f':
doType(VK_F);
break;
case 'g':
doType(VK_G);
break;
case 'h':
doType(VK_H);
break;
case 'i':
doType(VK_I);
break;
case 'j':
doType(VK_J);
break;
case 'k':
doType(VK_K);
break;
case 'l':
doType(VK_L);
break;
case 'm':
doType(VK_M);
break;
case 'n':
doType(VK_N);
break;
case 'o':
doType(VK_O);
break;
case 'p':
doType(VK_P);
break;
case 'q':
doType(VK_Q);
break;
case 'r':
doType(VK_R);
break;
case 's':
doType(VK_S);
break;
case 't':
doType(VK_T);
break;
case 'u':
doType(VK_U);
break;
case 'v':
doType(VK_V);
break;
case 'w':
doType(VK_W);
break;
case 'x':
doType(VK_X);
break;
case 'y':
doType(VK_Y);
break;
case 'z':
doType(VK_Z);
break;
case 'A':
doType(VK_SHIFT, VK_A);
break;
case 'B':
doType(VK_SHIFT, VK_B);
break;
case 'C':
doType(VK_SHIFT, VK_C);
break;
case 'D':
doType(VK_SHIFT, VK_D);
break;
case 'E':
doType(VK_SHIFT, VK_E);
break;
case 'F':
doType(VK_SHIFT, VK_F);
break;
case 'G':
doType(VK_SHIFT, VK_G);
break;
case 'H':
doType(VK_SHIFT, VK_H);
break;
case 'I':
doType(VK_SHIFT, VK_I);
break;
case 'J':
doType(VK_SHIFT, VK_J);
break;
case 'K':
doType(VK_SHIFT, VK_K);
break;
case 'L':
doType(VK_SHIFT, VK_L);
break;
case 'M':
doType(VK_SHIFT, VK_M);
break;
case 'N':
doType(VK_SHIFT, VK_N);
break;
case 'O':
doType(VK_SHIFT, VK_O);
break;
case 'P':
doType(VK_SHIFT, VK_P);
break;
case 'Q':
doType(VK_SHIFT, VK_Q);
break;
case 'R':
doType(VK_SHIFT, VK_R);
break;
case 'S':
doType(VK_SHIFT, VK_S);
break;
case 'T':
doType(VK_SHIFT, VK_T);
break;
case 'U':
doType(VK_SHIFT, VK_U);
break;
case 'V':
doType(VK_SHIFT, VK_V);
break;
case 'W':
doType(VK_SHIFT, VK_W);
break;
case 'X':
doType(VK_SHIFT, VK_X);
break;
case 'Y':
doType(VK_SHIFT, VK_Y);
break;
case 'Z':
doType(VK_SHIFT, VK_Z);
break;
case '`':
doType(VK_BACK_QUOTE);
break;
case '0':
doType(VK_0);
break;
case '1':
doType(VK_1);
break;
case '2':
doType(VK_2);
break;
case '3':
doType(VK_3);
break;
case '4':
doType(VK_4);
break;
case '5':
doType(VK_5);
break;
case '6':
doType(VK_6);
break;
case '7':
doType(VK_7);
break;
case '8':
doType(VK_8);
break;
case '9':
doType(VK_9);
break;
case '-':
doType(VK_MINUS);
break;
case '=':
doType(VK_EQUALS);
break;
case '~':
doType(VK_SHIFT, VK_BACK_QUOTE);
break;
case '!':
doType(VK_SHIFT, VK_1);
break;
case '@':
doType(VK_SHIFT, VK_1);
break;
case '#':
doType(VK_SHIFT, VK_3);
break;
case '$':
doType(VK_SHIFT, VK_4);
break;
case '%':
doType(VK_SHIFT, VK_5);
break;
case '^':
doType(VK_SHIFT, VK_6);
break;
case '&':
doType(VK_SHIFT, VK_7);
break;
case '*':
doType(VK_SHIFT, VK_8);
break;
case '(':
doType(VK_SHIFT, VK_9);
break;
case ')':
doType(VK_SHIFT, VK_0);
break;
case '_':
doType(VK_SHIFT, VK_MINUS);
break;
case '+':
doType(VK_SHIFT, VK_EQUALS);
break;
case '\t':
doType(VK_TAB);
break;
case '\n':
doType(VK_ENTER);
break;
case '\r':
doType(VK_ENTER);
break;
case '[':
doType(VK_OPEN_BRACKET);
break;
case ']':
doType(VK_CLOSE_BRACKET);
break;
case '\\':
doType(VK_BACK_SLASH);
break;
case '{':
doType(VK_SHIFT, VK_OPEN_BRACKET);
break;
case '}':
doType(VK_SHIFT, VK_CLOSE_BRACKET);
break;
case '|':
doType(VK_SHIFT, VK_BACK_SLASH);
break;
case ';':
doType(VK_SEMICOLON);
break;
case ':':
doType(VK_SHIFT, VK_SEMICOLON);
break;
case '\'':
doType(VK_QUOTE);
break;
case '"':
doType(VK_SHIFT, VK_QUOTE);
break;
case ',':
doType(VK_COMMA);
break;
case '<':
doType(VK_SHIFT, VK_COMMA);
break;
case '.':
doType(VK_PERIOD);
break;
case '>':
doType(VK_SHIFT, VK_PERIOD);
break;
case '/':
doType(VK_SLASH);
break;
case '?':
doType(VK_SHIFT, VK_SLASH);
break;
case ' ':
doType(VK_SPACE);
break;
default:
throw new IllegalArgumentException("Cannot type character " + c);
}
}
/**
* Type an entire String out, prints character by character to mimic user input.
*
* @param text
*/
@Override
public void type(String text) {
method.typeLine(text);
}
/**
* Take an entire file and type the entirety of it's contents. This will print
* character by character to mimic user input.
*
* @param f
* @throws IOException
*/
@Override
public void typeFile(File f) throws IOException {
method.typeFile(f);
}
/**
* Alter the delay between key presses in milliseconds
*
* @param delay
*/
@Override
public void setInputDelay(int delay) {
this.robo.setAutoDelay(Math.max(delay, Parameters.MIN_DELAY));
}
/**
* Get the delay between key presses in milliseconds
*
* @return
*/
@Override
public int getInputDelay() {
return this.robo.getAutoDelay();
}
/**
* Get the current state of the Keyboard
*
* @return
*/
@Override
public KeyboardMode getKeyboardMode() {
return this.method.mode();
}
/**
* Take a picture of the User's entire desktop and save it to
* cc-autotyper-crash.png <br />
* <br />
* This would be used in the case of an Uncaught Exception but currently is
* unused.
*/
@Override
public void writeCrashImage() {
try {
final File crashFile = new File("logs", "cc-autotyper-crash.png");
final BufferedImage img = this.robo.createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
ImageIO.write(img, "PNG", crashFile);
} catch (final IOException e) {
Console.exception(e);
}
}
@Override
public void setMethod(Methodology method) {
if (this.method != null)
Autotyper.unregisterGlobalKeyListener(this.method);
this.method = method;
Autotyper.registerGlobalKeyListener(this.method);
}
@Override
void press(int code) {
robo.keyPress(code);
}
@Override
void release(int code) {
robo.keyRelease(code);
}
/**
* Internal Method for pressing keys. Very convenient for Key Strokes. (i.e.
* Shift + 1 to get !). <br />
* <br />
* See {@link #doType(int[], int, int)}
*
* @param keycodes
*/
void doType(int... keycodes) {
doType(keycodes, 0, keycodes.length);
}
/**
* Recursively Press and Release Keys to mimic Key Strokes.
*
* @param codes
* @param offset
* @param length
*/
private void doType(int[] codes, int offset, int length) {
if (length == 0) return;
this.robo.keyPress(codes[offset]);
doType(codes, offset + 1, length - 1);
this.robo.keyRelease(codes[offset]);
}
@Override
public void destroy() {
this.method.destroy();
Autotyper.unregisterGlobalKeyListener(method);
}
}