From 4988cdc31e07e23a3bdf81205b2859ded5c0d5bb Mon Sep 17 00:00:00 2001 From: octopus_yan Date: Thu, 26 Sep 2024 21:01:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20GUI=E5=8D=95=E4=BE=8B=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/octopusyan/alistgui/Application.java | 92 ++++++++++++++++++- .../alistgui/controller/RootController.java | 3 +- 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/main/java/cn/octopusyan/alistgui/Application.java b/src/main/java/cn/octopusyan/alistgui/Application.java index 26169e5..79c106f 100644 --- a/src/main/java/cn/octopusyan/alistgui/Application.java +++ b/src/main/java/cn/octopusyan/alistgui/Application.java @@ -18,11 +18,12 @@ import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ProxySelector; +import java.io.*; +import java.net.*; import java.net.http.HttpClient; import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; public class Application extends javafx.application.Application { private static final Logger logger = LoggerFactory.getLogger(Application.class); @@ -32,6 +33,10 @@ public class Application extends javafx.application.Application { @Override public void init() { logger.info("application init ..."); + + // 单例模式检查 + makeSingle(); + // 初始化客户端配置 ConfigManager.load(); @@ -83,6 +88,7 @@ public class Application extends javafx.application.Application { primaryStage.setTitle(String.format("%s v%s", Constants.APP_TITLE, Constants.APP_VERSION)); Scene scene = Context.initScene(); primaryStage.setScene(scene); + primaryStage.show(); // 静默启动 @@ -113,4 +119,84 @@ public class Application extends javafx.application.Application { Platform.exit(); System.exit(0); } + + private static final int SINGLE_INSTANCE_LISTENER_PORT = 9009; + private static final String SINGLE_INSTANCE_FOCUS_MESSAGE = "focus"; + + private static final String instanceId = UUID.randomUUID().toString(); + + /** + * 我们在聚焦现有实例之前定义一个暂停 + * 因为有时启动实例的命令行或窗口 + * 可能会在第二个实例执行完成后重新获得焦点 + * 所以我们在聚焦原始窗口之前引入了一个短暂的延迟 + * 以便原始窗口可以保留焦点。 + */ + private static final int FOCUS_REQUEST_PAUSE_MILLIS = 500; + + /** + * 单实例检测 + * + * @see JavaFX单实例运行应用程序 + */ + public static void makeSingle() { + CountDownLatch instanceCheckLatch = new CountDownLatch(1); + + Thread instanceListener = new Thread(() -> { + try (ServerSocket serverSocket = new ServerSocket(SINGLE_INSTANCE_LISTENER_PORT, 10)) { + instanceCheckLatch.countDown(); + + while (true) { + try ( + Socket clientSocket = serverSocket.accept(); + BufferedReader in = new BufferedReader( + new InputStreamReader(clientSocket.getInputStream())) + ) { + String input = in.readLine(); + logger.info(STR."Received single instance listener message: \{input}"); + if (input.startsWith(SINGLE_INSTANCE_FOCUS_MESSAGE) && primaryStage != null) { + //noinspection BusyWait + Thread.sleep(FOCUS_REQUEST_PAUSE_MILLIS); + Platform.runLater(() -> { + logger.info(STR."To front \{instanceId}"); + primaryStage.setIconified(false); + primaryStage.show(); + primaryStage.toFront(); + }); + } + } catch (IOException e) { + logger.error("Single instance listener unable to process focus message from client"); + } + } + } catch (java.net.BindException b) { + logger.error("SingleInstanceApp already running"); + + try ( + Socket clientSocket = new Socket(InetAddress.getLocalHost(), SINGLE_INSTANCE_LISTENER_PORT); + PrintWriter out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream())) + ) { + logger.info("Requesting existing app to focus"); + out.println(STR."\{SINGLE_INSTANCE_FOCUS_MESSAGE} requested by \{instanceId}"); + } catch (IOException e) { + logger.error("", e); + } + + logger.info(STR."Aborting execution for instance \{instanceId}"); + Platform.exit(); + } catch (Exception e) { + logger.error("", e); + } finally { + instanceCheckLatch.countDown(); + } + }, "instance-listener"); + instanceListener.setDaemon(true); + instanceListener.start(); + + try { + instanceCheckLatch.await(); + } catch (InterruptedException e) { + //noinspection ResultOfMethodCallIgnored + Thread.interrupted(); + } + } } \ No newline at end of file diff --git a/src/main/java/cn/octopusyan/alistgui/controller/RootController.java b/src/main/java/cn/octopusyan/alistgui/controller/RootController.java index 99c85e5..efd9e83 100644 --- a/src/main/java/cn/octopusyan/alistgui/controller/RootController.java +++ b/src/main/java/cn/octopusyan/alistgui/controller/RootController.java @@ -114,12 +114,13 @@ public class RootController extends BaseController { @Override public void initViewAction() { closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> { + Platform.setImplicitExit(!ConfigManager.closeToTray()); if (ConfigManager.closeToTray()) { SystemTrayManager.show(); } else { SystemTrayManager.hide(); + Platform.exit(); } - Platform.setImplicitExit(!ConfigManager.closeToTray()); getWindow().close(); }); minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> getWindow().setIconified(true));