Files
autosqeal/src/main/java/wtf/beatrice/autosqueal/ui/MainWindow.java
T

187 lines
6.5 KiB
Java

package wtf.beatrice.autosqueal.ui;
import com.github.kwhat.jnativehook.keyboard.NativeKeyEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import wtf.beatrice.autosqueal.Main;
import wtf.beatrice.autosqueal.controls.CursorMover;
import wtf.beatrice.autosqueal.controls.RobotMouseTracker;
import wtf.beatrice.autosqueal.listener.CursorMoveListener;
import wtf.beatrice.autosqueal.util.RunnerUtil;
import wtf.beatrice.autosqueal.util.SystemUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class MainWindow
{
private static final Logger LOGGER = LogManager.getLogger(MainWindow.class);
private static final int WINDOW_HEIGHT = 700;
private static final int WINDOW_WIDTH = 800;
private static final long AUTOMATION_START_DELAY_SECONDS = 1L;
private static final long AWAY_POLL_INTERVAL_SECONDS = 1L;
private final JFrame frame = new JFrame();
private Button toggleButton;
private ScheduledExecutorService scheduler;
private CursorMover cursorMover;
/**
* Builds and shows the main window, and starts the automation.
* Must be called on the EDT.
*/
public void init() {
frame.setSize(new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT));
frame.setTitle("autosqueal");
frame.setResizable(false);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
LOGGER.info("Shutting down...");
stopAutomation();
Main.unregisterJNativeHook();
}
});
toggleButton = new Button();
toggleButton.setBounds(new Rectangle((WINDOW_WIDTH / 2) - 60, WINDOW_HEIGHT - 60, 120, 30));
toggleButton.addActionListener(e -> toggleRunning());
frame.add(toggleButton);
int bordersPx = 10;
int rescaleRateo = ((WINDOW_WIDTH - (2 * bordersPx)) * 100) / RunnerUtil.SCREEN_WIDTH;
int rescaleWidth = RunnerUtil.SCREEN_WIDTH * rescaleRateo / 100;
int rescaleHeight = RunnerUtil.SCREEN_HEIGHT * rescaleRateo / 100;
JLabel imageLabel = new JLabel(new ImageIcon(getScreenCapture(rescaleWidth, rescaleHeight)));
imageLabel.setBounds(new Rectangle(bordersPx, bordersPx, rescaleWidth, rescaleHeight));
frame.add(imageLabel);
Image preciseScreenshot = getPreciseScreenshot();
JLabel timestampLabel = new JLabel(new ImageIcon(preciseScreenshot));
timestampLabel.setBounds(new Rectangle(bordersPx, bordersPx + rescaleHeight + bordersPx, 100, 30));
frame.add(timestampLabel);
frame.setLayout(null);
frame.setVisible(true);
startAutomation();
updateToggleLabel();
}
private Image getScreenCapture(int rescaleWidth, int rescaleHeight) {
Image fullImage = getScreenCapture();
return fullImage.getScaledInstance(rescaleWidth, rescaleHeight, Image.SCALE_FAST);
}
public Image getPreciseScreenshot() {
if(SystemUtil.getHostSystem().equals(SystemUtil.OperatingSystem.MAC_OS)) {
BufferedImage screenshot = getScreenCapture();
return screenshot.getSubimage(RunnerUtil.SCREEN_WIDTH - 100,0, 100, 30);
}
return null;
}
public BufferedImage getScreenCapture() {
try {
Robot robot = new Robot();
return robot.createScreenCapture(new Rectangle(RunnerUtil.SCREEN_WIDTH, RunnerUtil.SCREEN_HEIGHT));
} catch (AWTException e) {
LOGGER.error("Robot initialization error", e);
}
return null;
}
public BufferedImage getBufferedImage(Image img) {
if (img instanceof BufferedImage image)
{
return image;
}
// Create a buffered image with transparency
BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
// Draw the image on to the buffered image
Graphics2D bGr = bimage.createGraphics();
bGr.drawImage(img, 0, 0, null);
bGr.dispose();
// Return the buffered image
return bimage;
}
/**
* Toggles the automation on or off. Safe to call from any thread: the actual
* work is always marshalled to the EDT, since it touches Swing components.
*/
public void toggleRunning() {
SwingUtilities.invokeLater(() -> {
if (cursorMover == null) {
startAutomation();
} else {
stopAutomation();
}
updateToggleLabel();
});
}
private void startAutomation() {
LOGGER.info("Starting automation");
scheduler = Executors.newScheduledThreadPool(3, runnable -> {
Thread thread = new Thread(runnable, "autosqueal-scheduler");
thread.setDaemon(true);
return thread;
});
// away-detection: polls the cursor position and ignores the movements
// the app performs itself, as reported by the robot mouse tracker.
// the automation only moves the mouse when the user is away.
RobotMouseTracker robotTracker = new RobotMouseTracker();
CursorMoveListener cursorMoveListener = new CursorMoveListener(robotTracker);
scheduler.scheduleWithFixedDelay(cursorMoveListener, 0L, AWAY_POLL_INTERVAL_SECONDS, TimeUnit.SECONDS);
cursorMover = new CursorMover(scheduler, robotTracker, cursorMoveListener::isUserAway);
scheduler.scheduleWithFixedDelay(cursorMover,
AUTOMATION_START_DELAY_SECONDS,
RunnerUtil.SECONDS_BETWEEN_MOVES,
TimeUnit.SECONDS);
}
private void stopAutomation() {
LOGGER.info("Stopping automation");
if (scheduler != null) {
scheduler.shutdownNow();
scheduler = null;
}
cursorMover = null;
}
private void updateToggleLabel() {
if (toggleButton == null) {
// the window is not initialized yet
return;
}
String hotkey = "[" + NativeKeyEvent.getKeyText(NativeKeyEvent.VC_CONTROL) + "]"
+ "[" + NativeKeyEvent.getKeyText(NativeKeyEvent.VC_ALT) + "]";
toggleButton.setLabel((cursorMover == null ? "Start " : "Stop ") + hotkey);
}
}