diff --git a/README.md b/README.md
index b5da8c0..51e851f 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Unified desktop integration for SciJava applications.
## Features
-The scijava-desktop component provides three kinds of desktop integration:
+The scijava-desktop component provides four kinds of desktop integration:
1. **URI Link Scheme Registration & Handling**
- Register custom URI schemes (e.g., `myapp://`) with the operating system
@@ -22,6 +22,17 @@ The scijava-desktop component provides three kinds of desktop integration:
- Associate file types with your application
- Platform-specific MIME type handling
+4. **Single-Instance Enforcement**
+ - Prevents multiple application instances when the OS re-launches the binary
+ - Uses a TCP socket listener via the `SingleInstance` feature of the
+ [SciJava App Launcher](https://github.com/scijava/app-launcher) to let a
+ secondary transient instance hand off its arguments to the already-running
+ primary instance and exit immediately -- before the splash screen appears
+ - Enabled via the `scijava.app.single-instance` system property
+ - Useful in conjunction with file type associations and URI scheme
+ registration, both of which trigger the OS to launch another copy of the
+ application binary when a registered file type or link is clicked
+
## Platform Support
- **Linux**: Full support for URI schemes and desktop icons via `.desktop` files
diff --git a/pom.xml b/pom.xml
index 6446688..7912073 100644
--- a/pom.xml
+++ b/pom.xml
@@ -104,10 +104,16 @@
11
bsd_2
SciJava developers.
+ 2.4.0-SNAPSHOT
+ true
+
+ org.scijava
+ app-launcher
+
org.scijava
scijava-common
diff --git a/src/main/java/org/scijava/desktop/DefaultDesktopService.java b/src/main/java/org/scijava/desktop/DefaultDesktopService.java
index 35f9ba1..cd2b221 100644
--- a/src/main/java/org/scijava/desktop/DefaultDesktopService.java
+++ b/src/main/java/org/scijava/desktop/DefaultDesktopService.java
@@ -28,9 +28,12 @@
*/
package org.scijava.desktop;
+import org.scijava.console.ConsoleService;
import org.scijava.event.ContextCreatedEvent;
import org.scijava.event.EventHandler;
+import org.scijava.launcher.SingleInstance;
import org.scijava.log.LogService;
+import org.scijava.main.MainService;
import org.scijava.object.LazyObjects;
import org.scijava.object.ObjectService;
import org.scijava.platform.PlatformService;
@@ -39,6 +42,7 @@
import org.scijava.prefs.PrefService;
import org.scijava.service.AbstractService;
import org.scijava.service.Service;
+import org.scijava.startup.StartupService;
import org.scijava.thread.ThreadService;
import java.io.BufferedReader;
@@ -68,6 +72,15 @@ public class DefaultDesktopService extends AbstractService implements DesktopSer
@Parameter
private ThreadService threadService;
+ @Parameter
+ private ConsoleService consoleService;
+
+ @Parameter
+ private MainService mainService;
+
+ @Parameter
+ private StartupService startupService;
+
@Parameter(required = false)
private PrefService prefs;
@@ -165,6 +178,15 @@ public String getDescription(final String extension) {
return descriptions.get(extension);
}
+ // -- Service methods --
+
+ @Override
+ public void initialize() {
+ if (isSingleInstanceEnabled()) {
+ startupService.addOperation(() -> SingleInstance.listen(0, this::receiveArgs));
+ }
+ }
+
// -- Event handlers --
@EventHandler
@@ -279,6 +301,16 @@ private Stream desktopPlatforms() {
.map(p -> (DesktopIntegrationProvider) p);
}
+ /**
+ * Receives arguments from a secondary transient application instance
+ * and handles them via standard SciJava Common service methods.
+ */
+ private void receiveArgs(String[] args) {
+ consoleService.processArgs(args);
+ mainService.execMains();
+ startupService.executeOperations();
+ }
+
private void maybeAutoInstallDesktopIntegrations() {
// Auto-install desktop integrations on first run.
diff --git a/src/main/java/org/scijava/desktop/DesktopService.java b/src/main/java/org/scijava/desktop/DesktopService.java
index 879328f..5608711 100644
--- a/src/main/java/org/scijava/desktop/DesktopService.java
+++ b/src/main/java/org/scijava/desktop/DesktopService.java
@@ -41,6 +41,10 @@
*/
public interface DesktopService extends SciJavaService {
+ default boolean isSingleInstanceEnabled() {
+ return Boolean.getBoolean("scijava.app.single-instance");
+ }
+
/**
* Applies desktop integration settings.
*