From 77497fd9f73440a62b681e5dd5a9942db7886169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beatrice=20Dellac=C3=A0?= Date: Thu, 1 Oct 2026 23:10:41 +0200 Subject: [PATCH] clean up on cmd-q and forced exits --- .../java/wtf/beatrice/autosqueal/Main.java | 8 ++++++ .../beatrice/autosqueal/ui/MainWindow.java | 28 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/main/java/wtf/beatrice/autosqueal/Main.java b/src/main/java/wtf/beatrice/autosqueal/Main.java index e5d8170..cc79d2d 100644 --- a/src/main/java/wtf/beatrice/autosqueal/Main.java +++ b/src/main/java/wtf/beatrice/autosqueal/Main.java @@ -21,6 +21,10 @@ public class Main { public static void main(String[] args) { LOGGER.info("Hello world!"); + // ⌘Q on macOS, the IDE stop button and signals all bypass + // windowClosing: make cleanup run on every exit path instead + Runtime.getRuntime().addShutdownHook(new Thread(mainWindow::cleanup, "autosqueal-shutdown")); + useSystemLookAndFeel(); boolean hookRegistered = registerJNativeHook(); @@ -66,6 +70,10 @@ public class Main { } public static void unregisterJNativeHook() { + if (!hookRegistered) { + return; + } + try { GlobalScreen.unregisterNativeHook(); LOGGER.info("Successfully unregistered jnativehook library!"); diff --git a/src/main/java/wtf/beatrice/autosqueal/ui/MainWindow.java b/src/main/java/wtf/beatrice/autosqueal/ui/MainWindow.java index ee35802..1724fae 100644 --- a/src/main/java/wtf/beatrice/autosqueal/ui/MainWindow.java +++ b/src/main/java/wtf/beatrice/autosqueal/ui/MainWindow.java @@ -12,6 +12,7 @@ import wtf.beatrice.autosqueal.util.SystemUtil; import javax.swing.*; import java.awt.*; +import java.awt.desktop.QuitStrategy; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; @@ -46,6 +47,9 @@ public class MainWindow private TrayIcon trayIcon; private MenuItem trayToggleItem; + /** Whether cleanup already ran, so it can safely be called twice. */ + private boolean cleanedUp = false; + /** * Builds and shows the main window, and starts the automation. * Must be called on the EDT. @@ -63,6 +67,17 @@ public class MainWindow } }); + // ⌘Q on macOS defaults to calling System.exit(0) directly, which + // bypasses windowClosing and skips cleanup: route quits through + // the window instead, so the hook is always released + if (Desktop.isDesktopSupported()) { + try { + Desktop.getDesktop().setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS); + } catch (UnsupportedOperationException ex) { + LOGGER.debug("Quit strategy not supported here", ex); + } + } + toggleButton = new JButton(); toggleButton.setBounds(new Rectangle((WINDOW_WIDTH / 2) - 60, WINDOW_HEIGHT - 60, 120, 30)); toggleButton.addActionListener(e -> toggleRunning()); @@ -167,8 +182,17 @@ public class MainWindow return image; } - /** Stops the automation and releases everything the app is holding. */ - private void cleanup() { + /** + * Stops the automation and releases everything the app is holding. + * Safe to call more than once, from any thread: window closing and + * JVM shutdown hooks can race each other on the way out. + */ + public synchronized void cleanup() { + if (cleanedUp) { + return; + } + cleanedUp = true; + LOGGER.info("Shutting down..."); stopAutomation();