package com.mattc.autotyper;
import static com.google.common.base.StandardSystemProperty.*;
import static com.mattc.autotyper.Strings.*;
import static com.mattc.autotyper.util.Console.logToFile;
import it.sauronsoftware.junique.AlreadyLockedException;
import it.sauronsoftware.junique.JUnique;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import com.mattc.autotyper.gui.AutotyperWindow;
import com.mattc.autotyper.gui.GuiAccessor;
import com.mattc.autotyper.gui.fx.FXAutotyperWindow;
import com.mattc.autotyper.gui.fx.FXGuiUtils;
import com.mattc.autotyper.gui.fx.WebLink;
import com.mattc.autotyper.robot.Keyboard;
import com.mattc.autotyper.robot.KeyboardMethodology;
import com.mattc.autotyper.util.Console;
import com.mattc.autotyper.util.IOUtils;
import com.mattc.autotyper.util.OS;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.jnativehook.keyboard.NativeKeyListener;
import javax.swing.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Arrays;
/**
* The Main Class of the Application meant to allow Minecraft Players who use
* ComputerCraft to use their favorite programs, even if the server blocks HTTP. <br />
* <br />
* The application uses JNativeHooks as an attempt ot provide cross-compatible
* keybindings that reach outside of the JVM (so a Swing GUI is not particularly
* required). As such some static methods are available for registering
* NativeKeyListeners as well as retrieving the Autotyper instance. It is meant to be
* a singleton.
*
* @author Matthew
*/
public class Autotyper {
private final GuiAccessor gui;
public Autotyper() {
if (FXGuiUtils.canUseJavaFX()) {
Console.debug("Using JavaFX GUI");
this.gui = FXAutotyperWindow.getAccessor();
} else {
Console.debug("Using Swing GUI");
this.gui = new AutotyperWindow();
}
}
/**
* Take the Program Arguments and execute the Autotyping procedure. Either using
* the program args or by loading up a GUI interface. <br />
*/
public void launch(String[] args) {
if (args[0].equalsIgnoreCase(FLAG_GUI)) {
this.gui.doShow();
// We have to terminate an FX GUI if it survives
if (!(this.gui instanceof AutotyperWindow)) {
System.exit(0);
}
} else {
printCopyrightStatement(false);
final Parameters params = parseArgs(args);
final Keyboard keys = Keyboard.retrieveKeyboard(KeyboardMethodology.TYPING);
keys.setInputDelay(params.inputDelay);
final File f = params.file;
try {
IOUtils.sleep(params.waitTime);
keys.typeFile(f);
} catch (IOException e) {
Console.exception(e);
} finally {
keys.destroy();
}
}
}
/**
* Take Arguments and parse flags as provided. Optional arguments are evaluated
* in the order they are provided. Ideally none should conflict.
*
* @return The File to extract the information of.
*/
private Parameters parseArgs(String[] args) {
File tmp;
int waitTime = Parameters.DEFAULT_WAIT, inputDelay = Parameters.DEFAULT_DELAY;
Console.info("Received Arguments: " + Arrays.toString(args));
// Parse Required Arguments which MUST be in the [flag|url|paste] <location>
// order.
switch (args[0]) {
case FLAG_FILE:
tmp = new File(args[1]);
if (!tmp.exists()) throw new IllegalArgumentException("No File Found At " + args[1] + "!");
break;
case FLAG_URL:
tmp = Downloader.getFile(args[1]).toFile();
break;
case FLAG_PASTE:
tmp = Downloader.getPastebin(args[1]).toFile();
break;
default:
tmp = null;
System.err.println("Invalid Arguments: " + Arrays.toString(args));
printUsage();
System.exit(-2);
}
// Parse Optional Arguments
for (int i = 2; i < args.length; i++) {
switch (args[i]) {
case FLAG_WAIT:
waitTime = Integer.parseInt(args[i + 1]) * 1000;
Console.debug("WaitTime set to " + waitTime + " milliseconds");
break;
case FLAG_INPUT_DELAY:
inputDelay = Integer.parseInt(args[i + 1]);
Console.debug("InputDelay set to " + inputDelay + " milliseconds");
break;
}
}
return new Parameters(waitTime, inputDelay, tmp);
}
/**
* Register a NativeKeyListener to receive ALL KeyEvents
*/
public static void registerGlobalKeyListener(NativeKeyListener listener) {
GlobalScreen.addNativeKeyListener(listener);
}
/**
* Unregister a NativeKeyListener
*/
public static void unregisterGlobalKeyListener(NativeKeyListener listener) {
GlobalScreen.removeNativeKeyListener(listener);
}
public static void main(String[] args) {
// Kill Error Stream [Prevent JNativeHooks Spam]
killSystemStreams();
// Acquire Application Lock or Fail Fast
try {
JUnique.acquireLock(Ref.APP_ID);
} catch (final AlreadyLockedException e) {
JOptionPane.showMessageDialog(null, String.format("Instance of %s Already Running!", Ref.APP_NAME), "Instance Collision!", JOptionPane.ERROR_MESSAGE);
System.exit(-1);
}
// Setup Native Libraries to handle Global Key Input
try {
GlobalScreen.registerNativeHook();
} catch (final NativeHookException e) {
Console.exception(e);
}
// If initial setup is ready, print Copyright
if (args.length == 0) {
printUsage();
System.exit(0);
}
Thread.setDefaultUncaughtExceptionHandler((t, e) -> Console.exception(e, "Caught by UncaughtExceptionHandler"));
Runtime.getRuntime().addShutdownHook(new Thread(Autotyper::onShutdown, "Shutdown"));
printSysInfo();
setLookAndFeel();
new Autotyper().launch(args);
}
/**
* Print out how the program should be executed to the Command Line
*/
private static void printUsage() {
final String std = "\t%-8s";
System.out.println();
System.out.println();
System.out.println(Ref.APP_NAME + " | " + Ref.VERSION + " by " + Ref.AUTHOR);
System.out.println("Usage: java -jar ccautotyper.jar [file|url|paste|gui] <location> [-wait] [-inputDelay]");
System.out.println(String.format(std, FLAG_FILE) + " - Indicates the file is on the local filesystem");
System.out.println(String.format(std, FLAG_URL) + " - Indicates the file must be downloaded");
System.out.println(String.format(std, FLAG_PASTE) + " - Indicates the file is located on pastebin");
System.out.println(String.format(std, FLAG_GUI) + " - Display GUI, No Other Parameters Required");
System.out.println(String.format(std, "Location") + " - Either the path to the file, the url, or the pastebin code");
System.out.println();
System.out.println("\t[Optional Parameters -- Order Does Not Matter]");
System.out.println(String.format(std, FLAG_WAIT) + " - The Seconds the Autotyper should wait before typing. [Default: 10]");
System.out.println(String.format(std, FLAG_INPUT_DELAY) + " - The Millisecond Delay between Key Strokes [Default: 40]");
System.out.println();
System.out.println("\tExample:");
System.out.println("\t\t" + Strings.EXAMPLE_EXECUTION);
System.out.println("\t\t Access the GUI using the 'gui' command argument by itself!");
System.out.println();
}
/**
* Print out a variety of System Properties to our log file.
*/
private static void printSysInfo() {
final String home = "JAVA_HOME: " + JAVA_HOME.value();
final String vendor = "JAVA_VEND: " + JAVA_VENDOR.value();
final String vendor_url = "JAVA_VURL: " + JAVA_VENDOR_URL.value();
final String jversion1 = "RUNNING Java " + JAVA_VERSION.value() + " compiled to class version " + JAVA_CLASS_VERSION.value();
final String jversion2 = "USING " + JAVA_VM_NAME.value() + " " + JAVA_VM_VERSION.value() + " by " + JAVA_VM_VENDOR.value() + " w/ compiler " + JAVA_COMPILER.value();
final String jversion3 = "SPECIFIED " + JAVA_SPECIFICATION_NAME.value() + " " + JAVA_SPECIFICATION_VERSION.value() + " by " + JAVA_SPECIFICATION_VENDOR.value();
final String jversion4 = "SPECIFIED " + JAVA_VM_SPECIFICATION_NAME.value() + " " + JAVA_VM_SPECIFICATION_VERSION.value() + " by " + JAVA_VM_SPECIFICATION_VENDOR.value();
final String os = "OS: " + OS_NAME.value() + " " + String.valueOf(OS.getArch()) + " " + OS_VERSION.value();
final String classpath = "CP: " + JAVA_CLASS_PATH.value();
final String cores = "TN: " + OS.processorCount() + " cores TMP_DIR: " + JAVA_IO_TMPDIR.value();
// Do not bother logging to console
logToFile("");
logToFile("========== SYS INFO ==========");
logToFile(home);
logToFile(vendor);
logToFile(vendor_url);
logToFile(jversion1);
logToFile(jversion2);
logToFile(os);
logToFile(classpath);
logToFile(cores);
logToFile(jversion3);
logToFile(jversion4);
logToFile("==============================");
logToFile("");
}
/**
* Grab and Print out Copyright Statement or Fail Fast
*/
public static void printCopyrightStatement(boolean gui) {
if (!gui) {
System.out.println();
}
InputStream is = null;
ByteArrayOutputStream bos = null;
try {
final byte[] buf = new byte[8192];
is = Resources.getLicense().stream();
bos = new ByteArrayOutputStream();
if (is == null) {
Console.error("Copyright Statement Not Found! Exiting...");
IOUtils.close(bos);
System.exit(-3);
}
for (int c = is.read(buf); c != -1; c = is.read(buf)) {
bos.write(buf, 0, c);
}
if (gui) {
if (FXGuiUtils.canUseJavaFX() && Platform.isFxApplicationThread()) {
WebLink repoLink = new WebLink("CCAutotyper @ GitHub", Strings.GITHUB_URL, FXAutotyperWindow.getAccessor());
WebLink devLink = new WebLink("Developer Website", Strings.DEVSITE_URL, FXAutotyperWindow.getAccessor());
Alert alert = FXGuiUtils.buildLongAlert("This Software is Licensed with No Warranty", bos.toString(), repoLink, devLink);
alert.setTitle("Copyright & Warranty");
alert.show();
} else {
JOptionPane.showMessageDialog(null, bos.toString(), "Copyright & Warranty", JOptionPane.INFORMATION_MESSAGE);
}
} else {
System.out.println(bos.toString());
System.out.println();
}
} catch (final Exception e) {
Console.exception(e);
} finally {
IOUtils.close(is);
IOUtils.close(bos);
}
}
/**
* Attempt to use System Look And Feel to look Most "Natural".
* <p>
* If all else fails, attempt to create a consistent experience using the Cross
* Platform Look and Feel.
* <p>
* If that fails... somehow... then use the Default Look and Feel.
*/
private static void setLookAndFeel() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
Console.info("SUCCESS! LookAndFeel set to System LookAndFeel -- " + UIManager.getSystemLookAndFeelClassName() + "!");
} catch (final Exception e) {
Console.warn("Unable to Set LookAndFeel to System LookAndFeel -- " + e.getMessage() + "...");
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
Console.info("SUCCESS! Look and Feel Set to Cross-Platform LookAndFeel!");
} catch (final Exception ex) {
Console.bigWarning("FAILURE! Failure to Set Look And Feel! Using Default...");
Console.exception(ex);
}
}
}
private static void killSystemStreams() {
// Filter out RTextAreaBase's 'yo'
System.setOut(new PrintStream(System.out) {
@Override
public void print(String s) {
super.print(s);
}
// Correct for RTextAreaBase Printing "Yo"
@Override
public void println(String s) {
if (!s.equalsIgnoreCase("yo"))
super.println(s);
}
});
// Kill JNativeHooks incessant output
System.setErr(new PrintStream(NULL_STREAM));
}
// Finalization Method called if JVM exits properly
// is a Runtime Shutdown Hook
private static void onShutdown() {
Console.debug("Finalizing Registers...");
JUnique.releaseLock(Ref.APP_ID);
try {
GlobalScreen.unregisterNativeHook();
} catch (NativeHookException e) {
Console.exception(e);
}
}
// Do nothing with any input
private static final OutputStream NULL_STREAM = new OutputStream() {
@Override
public void write(int b) throws IOException {
}
};
}