package com.mattc.autotyper.robot; import static java.awt.event.KeyEvent.VK_BACK_SPACE; import com.google.common.collect.Queues; import com.mattc.autotyper.util.Console; import com.mattc.autotyper.util.IOUtils; import org.jnativehook.keyboard.NativeKeyEvent; import org.jnativehook.keyboard.NativeKeyListener; import java.io.File; import java.io.IOException; import java.util.Queue; /** * Base Implementation for {@link Methodology} objects, which define how a {@link Keyboard} should * handle input. <br /> * <br /> * The benefit of extending BaseMethodology include a working implementation of {@link NativeKeyListener} * which can also pause input and handle key combinations based on alt, particularly (alt-S and alt-p for stop and play * respectively though this will be expanded in the future to allow arbitrary alt-combination or even just * arbitrary combinations), and a schedule system that handles communication between the NativeKey Thread * and the Main Thread. * * @author Matthew * Created 4/3/2015 at 4:53 PM */ public abstract class BaseMethodology implements Methodology { // This queue is maintained for key presses that must be communicated to the Native Key Thread private final Queue<Integer> schedule = Queues.newConcurrentLinkedQueue(); private volatile Keyboard.KeyboardMode mode = Keyboard.KeyboardMode.INACTIVE; private volatile boolean alt = false; private volatile boolean bspace = false; private volatile boolean keypressed = false; @Override public abstract void type(char c); @Override public abstract void typeLine(String line); @Override public abstract void typeFile(File file) throws IOException; @Override public Keyboard.KeyboardMode mode() { return mode; } /** * @return If Current KeyboardMode == KeyboardMode.ACTIVE */ public boolean isActive() { return mode == Keyboard.KeyboardMode.ACTIVE; } /** * @return If Current KeyboardMode == KeyboardMode.PAUSED */ public boolean isPaused() { return mode == Keyboard.KeyboardMode.PAUSED; } /** * @return If Current KeyboardMode == KeyboardMode.INACTIVE */ public boolean isInactive() { return mode == Keyboard.KeyboardMode.INACTIVE; } /** * Will return true if it is determined that the <em>Left</em> Alt Key is down. * * @return true if Left alt is down */ public boolean isAltDown() { return alt; } @Override public void nativeKeyTyped(NativeKeyEvent e) { // Ignore if alt is not pressed, it's all we care about. if (!this.alt) return; String log = this.mode.name() + "..."; if (this.alt && (e.getKeyChar() == 'p')) { // Toggle for Alt + P switch (this.mode) { case ACTIVE: this.mode = Keyboard.KeyboardMode.PAUSED; break; case PAUSED: this.mode = Keyboard.KeyboardMode.ACTIVE; break; default: break; } } else if (this.alt && (e.getKeyChar() == 's')) { if (this.mode == Keyboard.KeyboardMode.PAUSED) schedule.add(VK_BACK_SPACE); // Terminate Current Session for Alt + S this.mode = Keyboard.KeyboardMode.INACTIVE; schedule.add(VK_BACK_SPACE); this.alt = false; } log = "Keyboard set to " + this.mode.name() + " from " + log; Console.debug(log); IOUtils.sleep(20); } @Override public void nativeKeyReleased(NativeKeyEvent e) { // If Keyboard is Active and Left Alt is released if ((e.getKeyCode() == NativeKeyEvent.VC_ALT_L) && (this.mode == Keyboard.KeyboardMode.ACTIVE)) { this.alt = false; // Delete the 1 or 2 stray characters // Alt + P will print P in computer craft, this deletes the P // if the user did not. if (!this.bspace && this.keypressed) { schedule.add(VK_BACK_SPACE); } else { this.bspace = false; } if (this.keypressed) schedule.add(VK_BACK_SPACE); } } @Override public void nativeKeyPressed(NativeKeyEvent e) { // If Left Alt is Pressed, set the Alt Flag to true. if (e.getKeyCode() == NativeKeyEvent.VC_ALT_L) { this.alt = true; } else if (this.alt && (e.getKeyCode() == NativeKeyEvent.VC_BACKSPACE)) { this.bspace = true; } else if (this.alt && (e.getKeyCode() == NativeKeyEvent.VC_P || e.getKeyCode() == NativeKeyEvent.VC_S)) { this.keypressed = true; } } /* Returns true if the scheduling queue is empty, false otherwise (essentially a call to queue.isEmpty) */ protected boolean isScheduleEmpty() { return schedule.isEmpty(); } /* Returns the next key in the schedule, if any. */ protected int nextScheduleKey() { return schedule.remove(); } /* Clears the current scheduling queue. */ protected void clearSchedule() { schedule.clear(); } protected void start() { setMode(Keyboard.KeyboardMode.ACTIVE); } protected void pause() { setMode(Keyboard.KeyboardMode.PAUSED); } protected void end() { setMode(Keyboard.KeyboardMode.INACTIVE); } protected void setMode(Keyboard.KeyboardMode mode) { this.mode = mode; } }