fixed terminate

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Radek Davidek 2026-04-30 18:23:16 +02:00
parent 0998ba3762
commit b3cf1825d9
3 changed files with 157 additions and 69 deletions

View File

@ -7,40 +7,43 @@ import java.util.Set;
import javax.swing.JLabel;
/**
* Globální hotkey listener pro detekci CTRL+ALT+Z kombinace
* Funguje i když není okno fokusované
* Globální hotkey listener pro detekci CTRL+ALT+Q.
* Na Linuxu funguje i bez fokusu přes nativní polling X11.
*/
public class GlobalHotkey extends JLabel {
private static GlobalHotkey instance;
private GlobalKeyListener globalKeyListener;
private static final long HOTKEY_COOLDOWN_MS = 700;
private final GlobalKeyListener globalKeyListener;
private final Runnable onHotkey;
private volatile boolean running = true;
private volatile long lastHotkeyTrigger = 0L;
private Thread pollingThread;
public GlobalHotkey(Runnable onHotkey) {
this.globalKeyListener = new GlobalKeyListener(onHotkey);
this.onHotkey = onHotkey;
this.globalKeyListener = new GlobalKeyListener();
// Registrace globálního key listeneru pro případy, kdy aplikace fokus
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addKeyEventDispatcher(globalKeyListener);
// Spustit nativní polling pro globální STOP hotkey na Linuxu (funguje i bez fokusu)
if (System.getProperty("os.name").toLowerCase().contains("linux")) {
Thread pollingThread = new Thread(() -> {
while (true) {
// Nativní polling globální hotkey na Linuxu (funguje i bez fokusu)
if (isLinux()) {
pollingThread = new Thread(() -> {
while (running && !Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(150);
// Kontrola CTRL+ALT+X globálně
// XK_Control_L/R: 0xFFE3/4, XK_Alt_L/R: 0xFFE9/A, XK_x: 0x78, XK_X: 0x58
// XK_Control_L/R: 0xFFE3/4, XK_Alt_L/R: 0xFFE9/A, XK_q: 0x71, XK_Q: 0x51
boolean ctrl = WindowFinder.isKeyPressedGlobally(0xFFE3) || WindowFinder.isKeyPressedGlobally(0xFFE4);
boolean alt = WindowFinder.isKeyPressedGlobally(0xFFE9) || WindowFinder.isKeyPressedGlobally(0xFFEA);
boolean xPressed = WindowFinder.isKeyPressedGlobally(0x78) || WindowFinder.isKeyPressedGlobally(0x58);
boolean qPressed = WindowFinder.isKeyPressedGlobally(0x71) || WindowFinder.isKeyPressedGlobally(0x51);
if (ctrl && alt && xPressed) {
if (onHotkey != null) {
System.out.println("🔌 Globální NATIVNÍ hotkey detekován: CTRL+ALT+X");
onHotkey.run();
Thread.sleep(1000); // Prevence vícenásobného spuštění
}
if (ctrl && alt && qPressed) {
triggerHotkey("Globální NATIVNÍ hotkey detekován: CTRL+ALT+Q");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
// Ignorovat chyby v pollingu
@ -62,7 +65,7 @@ public class GlobalHotkey extends JLabel {
public boolean isKeyPressed(int keyCode) {
// Na Linuxu pro šipky používáme výhradně nativní globální detekci.
// Standardní KeyEventDispatcher se může "zaseknout", pokud okno ztratí fokus během stisku.
if (System.getProperty("os.name").toLowerCase().contains("linux")) {
if (isLinux()) {
if (keyCode == KeyEvent.VK_DOWN) {
try {
return WindowFinder.isKeyPressedGlobally(0xFF54); // XK_Down
@ -76,17 +79,40 @@ public class GlobalHotkey extends JLabel {
return globalKeyListener.isKeyPressed(keyCode);
}
public void cleanup() {
running = false;
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.removeKeyEventDispatcher(globalKeyListener);
if (pollingThread != null) {
pollingThread.interrupt();
pollingThread = null;
}
}
private static boolean isLinux() {
return System.getProperty("os.name").toLowerCase().contains("linux");
}
private void triggerHotkey(String logMessage) {
long now = System.currentTimeMillis();
if (now - lastHotkeyTrigger < HOTKEY_COOLDOWN_MS) {
return;
}
lastHotkeyTrigger = now;
if (onHotkey != null) {
System.out.println("🔌 " + logMessage);
onHotkey.run();
}
}
/**
* Interní třída pro globální naslouchání na klávesnici
*/
private static class GlobalKeyListener implements java.awt.KeyEventDispatcher {
private Runnable callback;
private class GlobalKeyListener implements java.awt.KeyEventDispatcher {
private Set<Integer> pressedKeys = new HashSet<>();
public GlobalKeyListener(Runnable callback) {
this.callback = callback;
}
public boolean isKeyPressed(int keyCode) {
return pressedKeys.contains(keyCode);
}
@ -96,13 +122,10 @@ public class GlobalHotkey extends JLabel {
if (e.getID() == KeyEvent.KEY_PRESSED) {
pressedKeys.add(e.getKeyCode());
// Detekce CTRL+ALT+X
if (e.getKeyCode() == KeyEvent.VK_X &&
// Detekce CTRL+ALT+Q
if (e.getKeyCode() == KeyEvent.VK_Q &&
e.isControlDown() && e.isAltDown()) {
System.out.println("🔌 Globální hotkey detekován: CTRL+ALT+X");
if (callback != null) {
callback.run();
}
triggerHotkey("Globální hotkey detekován: CTRL+ALT+Q");
return true; // Konzumovat event
}
} else if (e.getID() == KeyEvent.KEY_RELEASED) {

View File

@ -1,15 +1,11 @@
package com.pokemongo;
import java.awt.AWTException;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
@ -35,6 +31,7 @@ public class PokemonGoAutomation {
private static final int DELAY_AFTER_ACTION = 100; // ms
private int transferredPokemonCount = 0; // Počet transfernutých pokémonů
private Point initialMousePosition = null; // Pozice kurzoru před začátkem automatizace
private volatile boolean stopRequested = false;
public PokemonGoAutomation() throws AWTException {
this.robot = new Robot();
@ -57,6 +54,14 @@ public class PokemonGoAutomation {
transferredPokemonCount = 0;
}
public void requestStop() {
stopRequested = true;
}
private boolean shouldStop() {
return stopRequested || Thread.currentThread().isInterrupted();
}
/**
* Pořídí screenshot oblasti okna
*/
@ -120,6 +125,10 @@ public class PokemonGoAutomation {
if (template == null)
return 0.0;
if (shouldStop()) {
return 0.0;
}
int tWidth = template.getWidth();
int tHeight = template.getHeight();
@ -136,6 +145,9 @@ public class PokemonGoAutomation {
int requiredMatch = (int) (totalPixels * 0.75); // 75% shody
for (int y = 0; y < tHeight; y++) {
if (shouldStop()) {
return 0.0;
}
for (int x = 0; x < tWidth; x++) {
int templateRGB = template.getRGB(x, y);
int screenRGB = screenshot.getRGB(startX + x, startY + y);
@ -175,6 +187,10 @@ public class PokemonGoAutomation {
private Point findConfirmTransferButtonPosition(BufferedImage template, int tolerance) {
System.out.println("Hledám potvrzovací TRANSFER tlačítko pomocí template matchingu...");
if (shouldStop()) {
return null;
}
BufferedImage screenshot = captureScreen(windowBounds);
int tWidth = template.getWidth();
@ -190,6 +206,9 @@ public class PokemonGoAutomation {
// Skenovat Y-ové pozice s větším krokem - 15px místo 5px pro zrychlení
for (int y = searchStartY; y <= searchEndY - tHeight; y += 15) {
if (shouldStop()) {
return null;
}
// Skenovat také X pozice pro nalezení středu shody
for (int x = 0; x <= windowBounds.width - tWidth; x += 30) {
double score = templateMatch(screenshot, x, y, template, tolerance);
@ -218,6 +237,10 @@ public class PokemonGoAutomation {
private Point findIncludeButtonPosition(int tolerance) {
System.out.println("Hledám tlačítko INCLUDE pomocí template matchingu (include.png)...");
if (shouldStop()) {
return null;
}
BufferedImage screenshot = captureScreen(windowBounds);
int tWidth = includeTemplate.getWidth();
@ -233,6 +256,9 @@ public class PokemonGoAutomation {
// Skenovat Y-ové pozice s větším krokem - 15px místo 5px pro zrychlení
for (int y = searchStartY; y <= searchEndY - tHeight; y += 15) {
if (shouldStop()) {
return null;
}
// Skenovat také X pozice pro nalezení středu shody
for (int x = 0; x <= windowBounds.width - tWidth; x += 30) {
double score = templateMatch(screenshot, x, y, includeTemplate, tolerance);
@ -309,6 +335,10 @@ public class PokemonGoAutomation {
* Stiskne tlačítko Transfer na spodní části obrazovky
*/
public void clickTransferButton() {
if (shouldStop()) {
return;
}
// Použít detekci tlačítka
// Point buttonPos = findTransferButtonPosition();
Point buttonPos = getAbsolutePoint(300, 1156);
@ -334,6 +364,10 @@ public class PokemonGoAutomation {
public void clickConfirmTransferButton(BufferedImage template, int tolerance) {
System.out.println("Potvrzuji transfer - hledám confirmation button...");
if (shouldStop()) {
return;
}
robot.delay(100); // Počkat na zobrazení dialogu
// Najít zelené TRANSFER tlačítko v potvrzovacím dialogu
@ -356,6 +390,10 @@ public class PokemonGoAutomation {
public boolean clickIncludeButton(int tolerance) {
System.out.println("Potvrzuji transfer - hledám include.png (confirmation button)...");
if (shouldStop()) {
return false;
}
robot.delay(100); // Počkat na zobrazení dialogu
// Najít zelené INCLUDE tlačítko v potvrzovacím dialogu
@ -409,7 +447,7 @@ public class PokemonGoAutomation {
try {
while (transferredCount < totalPokemonCount) {
// Kontrola přerušení
if (Thread.currentThread().isInterrupted()) {
if (shouldStop()) {
System.out.println("\n⚠ Automatizace byla přerušena!");
return;
}
@ -423,9 +461,19 @@ public class PokemonGoAutomation {
// Vybrat pokémony a získat skutečný počet vybraných
int actualTransferredCount = selectAllPokemonCount(pokemonThisRound);
if (shouldStop()) {
System.out.println("\n⚠ Automatizace byla přerušena během označování!");
return;
}
System.out.println("Čekám na TRANSFER...");
clickTransferButton();
if (shouldStop()) {
System.out.println("\n⚠ Automatizace byla přerušena po kliknutí na TRANSFER!");
return;
}
System.out.println("Čekám na INCLUDE dialog...");
robot.delay(100);
if (clickIncludeButton(110)) {
@ -450,7 +498,7 @@ public class PokemonGoAutomation {
// Čekat s kontrolou přerušení
long endTime = System.currentTimeMillis() + (delaySeconds * 1000L);
while (System.currentTimeMillis() < endTime) {
if (Thread.currentThread().isInterrupted()) {
if (shouldStop()) {
System.out.println("\n⚠ Automatizace byla přerušena během přestávky!");
break;
}
@ -487,6 +535,11 @@ public class PokemonGoAutomation {
System.out.println("Budu označovat " + maxPokemon + " pokémonů...");
for (int i = 0; i < maxPokemon; i++) {
if (shouldStop()) {
System.out.println("Označování přerušeno.");
return i;
}
Point pos = positions.get(i);
System.out.println("Klikám na Pokémona " + (i + 1) + "/" + maxPokemon + " na pozici: " + pos);

View File

@ -96,7 +96,7 @@ public class PokemonGoGUI extends JFrame {
versionLabel.setFont(SMALL_FONT);
versionLabel.setForeground(new Color(120, 120, 120));
stopButton = new JButton("⏹ ZASTAVIT (CTRL+ALT+X)");
stopButton = new JButton("⏹ ZASTAVIT (CTRL+ALT+Q)");
stopButton.setPreferredSize(new Dimension(150, 30));
styleButton(stopButton, PRIMARY_RED, Color.WHITE, SMALL_FONT);
stopButton.setEnabled(false);
@ -261,8 +261,8 @@ public class PokemonGoGUI extends JFrame {
add(mainPanel);
// Klávesová zkratka: Ctrl+Alt+Z pro zastavení
KeyStroke hotkey = KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK);
// Klávesová zkratka: Ctrl+Alt+Q pro zastavení
KeyStroke hotkey = KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK);
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(hotkey, "stopAutomationAction");
getRootPane().getActionMap().put("stopAutomationAction", new AbstractAction() {
@Override
@ -273,7 +273,7 @@ public class PokemonGoGUI extends JFrame {
}
});
// Globální hotkey pro ESC (bonus, když okno není fokusované)
// Globální hotkey (funguje i když okno není fokusované)
globalHotkey = new GlobalHotkey(new Runnable() {
@Override
public void run() {
@ -290,7 +290,7 @@ public class PokemonGoGUI extends JFrame {
logArea.append("=== Pokémon GO Automatizace ===\n\n");
logArea.append("Vyberte jednu z automatizací a klikněte na tlačítko START.\n");
logArea.append("Ujistěte se, že je aplikace Pokémon GO spuštěná.\n");
logArea.append("Pro zastavení stiskněte Ctrl+Alt+X nebo klikněte ZASTAVIT.\n\n");
logArea.append("Pro zastavení stiskněte Ctrl+Alt+Q nebo klikněte ZASTAVIT.\n\n");
}
/**
@ -482,6 +482,7 @@ public class PokemonGoGUI extends JFrame {
}
// Získat počet pokémonů a čekání z GUI
@SuppressWarnings("unchecked")
JComboBox<Integer> countComboBox = (JComboBox<Integer>) transferCard.getClientProperty("countComboBox");
JSpinner delaySpinner = (JSpinner) transferCard.getClientProperty("delaySpinner");
@ -742,10 +743,17 @@ public class PokemonGoGUI extends JFrame {
* Zastaví automatizaci
*/
private void stopAutomation() {
if (!isRunning && !autoClickRunning) {
return;
}
shouldStop = true;
autoClickRunning = false;
isRunning = false;
stopButton.setEnabled(false);
if (automation != null) {
automation.requestStop();
}
if (automationThread != null && automationThread.isAlive()) {
automationThread.interrupt();
@ -760,6 +768,7 @@ public class PokemonGoGUI extends JFrame {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
stopButton.setEnabled(false);
logArea.append("\n⚠ Automatizace byla přerušena uživatelem\n");
statusLabel.setText("Přerušeno");
statusLabel.setForeground(new Color(244, 67, 54));
@ -852,6 +861,9 @@ public class PokemonGoGUI extends JFrame {
@Override
public void windowClosing(WindowEvent e) {
System.out.println("Zavírám okno...");
if (frame.globalHotkey != null) {
frame.globalHotkey.cleanup();
}
frame.saveSettings();
System.exit(0);
}