diff --git a/build.gradle b/build.gradle
index ec8db57e..5908ee76 100644
--- a/build.gradle
+++ b/build.gradle
@@ -71,6 +71,9 @@ dependencies {
}
implementation('dev.bwt:bwt-jni:0.1.7')
implementation('net.sourceforge.javacsv:javacsv:2.0')
+ implementation('tk.pratanumandal:unique4j:1.3') {
+ exclude group: 'com.google.code.gson'
+ }
implementation('org.slf4j:jul-to-slf4j:1.7.30') {
exclude group: 'org.slf4j'
}
@@ -151,7 +154,8 @@ jlink {
"--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow",
"--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow",
"--add-opens=java.base/java.net=com.sparrowwallet.sparrow",
- "--add-reads=com.sparrowwallet.merged.module=java.desktop"]
+ "--add-reads=com.sparrowwallet.merged.module=java.desktop",
+ "--add-reads=com.sparrowwallet.merged.module=java.sql"]
if(os.macOsX) {
jvmArgs += "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module"
diff --git a/src/main/deploy/package/osx/Info.plist b/src/main/deploy/package/osx/Info.plist
index 1ffca736..ce69f8d7 100644
--- a/src/main/deploy/package/osx/Info.plist
+++ b/src/main/deploy/package/osx/Info.plist
@@ -54,5 +54,69 @@
+ UTImportedTypeDeclarations
+
+
+ UTTypeIdentifier
+ org.bitcoin.psbt
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ psbt
+
+
+ UTTypeDescription
+ Partially Signed Bitcoin Transaction
+ UTTypeIconFile
+ sparrow.icns
+
+
+ UTTypeIdentifier
+ org.bitcoin.txn
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ txn
+
+
+ UTTypeDescription
+ Bitcoin Transaction
+ UTTypeIconFile
+ sparrow.icns
+
+
+ CFBundleDocumentTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleTypeIconFile
+ sparrow.icns
+ CFBundleTypeExtensions
+
+ psbt
+
+ CFBundleTypeName
+ Partially Signed Bitcoin Transaction
+ LSHandlerRank
+ Default
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleTypeIconFile
+ sparrow.icns
+ CFBundleTypeExtensions
+
+ txn
+
+ CFBundleTypeName
+ Bitcoin Transaction
+ LSHandlerRank
+ Default
+
+
\ No newline at end of file
diff --git a/src/main/deploy/txn.properties b/src/main/deploy/txn.properties
new file mode 100644
index 00000000..88f09331
--- /dev/null
+++ b/src/main/deploy/txn.properties
@@ -0,0 +1,3 @@
+mime-type=application/bitcoin-transaction
+extension=txn
+description=Bitcoin Transaction
\ No newline at end of file
diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java
index 2be63450..3a45ba0c 100644
--- a/src/main/java/com/sparrowwallet/sparrow/AppController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java
@@ -691,11 +691,6 @@ public class AppController implements Initializable {
}
}
- private boolean isWalletFile(File file) {
- FileType fileType = IOUtils.getFileType(file);
- return FileType.JSON.equals(fileType) || FileType.BINARY.equals(fileType);
- }
-
private void setServerToggleTooltip(Integer currentBlockHeight) {
Tooltip tooltip = new Tooltip(getServerToggleTooltipText(currentBlockHeight));
tooltip.setShowDuration(Duration.seconds(15));
@@ -1808,14 +1803,22 @@ public class AppController implements Initializable {
@Subscribe
public void requestWalletOpen(RequestWalletOpenEvent event) {
if(tabs.getScene().getWindow().equals(event.getWindow())) {
- openWallet(true);
+ if(event.getFile() != null) {
+ openWalletFile(event.getFile(), true);
+ } else {
+ openWallet(true);
+ }
}
}
@Subscribe
public void requestTransactionOpen(RequestTransactionOpenEvent event) {
if(tabs.getScene().getWindow().equals(event.getWindow())) {
- openTransactionFromFile(null);
+ if(event.getFile() != null) {
+ openTransactionFile(event.getFile());
+ } else {
+ openTransactionFromFile(null);
+ }
}
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java
index 3037011c..00072953 100644
--- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java
+++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java
@@ -13,10 +13,7 @@ import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.control.TextUtils;
import com.sparrowwallet.sparrow.control.TrayManager;
import com.sparrowwallet.sparrow.event.*;
-import com.sparrowwallet.sparrow.io.Config;
-import com.sparrowwallet.sparrow.io.Device;
-import com.sparrowwallet.sparrow.io.Hwi;
-import com.sparrowwallet.sparrow.io.Storage;
+import com.sparrowwallet.sparrow.io.*;
import com.sparrowwallet.sparrow.net.*;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
@@ -45,13 +42,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.*;
-import java.awt.desktop.OpenURIEvent;
+import java.awt.desktop.OpenFilesHandler;
import java.awt.desktop.OpenURIHandler;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URI;
+import java.net.URISyntaxException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
@@ -104,6 +102,10 @@ public class AppServices {
private static List devices;
+ private static final List argFiles = new ArrayList<>();
+
+ private static final List argUris = new ArrayList<>();
+
private static final Map payjoinURIs = new HashMap<>();
private final ChangeListener onlineServicesListener = new ChangeListener<>() {
@@ -124,12 +126,11 @@ public class AppServices {
};
private static final OpenURIHandler openURIHandler = event -> {
- URI uri = event.getURI();
- if("bitcoin".equals(uri.getScheme())) {
- Platform.runLater(() -> openBitcoinUri(uri));
- } else if("aopp".equals(uri.getScheme())) {
- Platform.runLater(() -> openAddressOwnershipProof(uri));
- }
+ openURI(event.getURI());
+ };
+
+ private static final OpenFilesHandler openFilesHandler = event -> {
+ openFiles(event.getFiles(), null);
};
public AppServices(MainApp application) {
@@ -521,6 +522,11 @@ public class AppServices {
ElectrumServer.clearRetrievedScriptHashes(wallet);
}
+ public static boolean isWalletFile(File file) {
+ FileType fileType = IOUtils.getFileType(file);
+ return FileType.JSON.equals(fileType) || FileType.BINARY.equals(fileType);
+ }
+
public static Optional showWarningDialog(String title, String content, ButtonType... buttons) {
return showAlertDialog(title, content, Alert.AlertType.WARNING, buttons);
}
@@ -611,14 +617,71 @@ public class AppServices {
}
}
- public static void handleURI(URI uri) {
- openURIHandler.openURI(new OpenURIEvent(uri));
+ static void parseFileUriArguments(List fileUriArguments) {
+ for(String fileUri : fileUriArguments) {
+ try {
+ File file = new File(fileUri.replace("~", System.getProperty("user.home")));
+ if(file.exists()) {
+ argFiles.add(file);
+ continue;
+ }
+ URI uri = new URI(fileUri);
+ argUris.add(uri);
+ } catch(URISyntaxException e) {
+ log.warn("Could not parse " + fileUri + " as a valid file or URI");
+ } catch(Exception e) {
+ //ignore
+ }
+ }
+ }
+
+ public static void openFileUriArguments(Window window) {
+ openFiles(argFiles, window);
+ argFiles.clear();
+
+ for(URI argUri : argUris) {
+ openURI(argUri);
+ }
+ argUris.clear();
+ }
+
+ private static void openFiles(List files, Window window) {
+ final List openFiles = new ArrayList<>(files);
+ Platform.runLater(() -> {
+ Window openWindow = window;
+ if(openWindow == null) {
+ openWindow = getActiveWindow();
+ }
+
+ for(File file : openFiles) {
+ if(isWalletFile(file)) {
+ EventManager.get().post(new RequestWalletOpenEvent(openWindow, file));
+ } else {
+ EventManager.get().post(new RequestTransactionOpenEvent(openWindow, file));
+ }
+ }
+ });
+ }
+
+ private static void openURI(URI uri) {
+ Platform.runLater(() -> {
+ if("bitcoin".equals(uri.getScheme())) {
+ openBitcoinUri(uri);
+ } else if("aopp".equals(uri.getScheme())) {
+ openAddressOwnershipProof(uri);
+ }
+ });
}
public static void addURIHandlers() {
try {
- if(Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_URI)) {
- Desktop.getDesktop().setOpenURIHandler(openURIHandler);
+ if(Desktop.isDesktopSupported()) {
+ if(Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_FILE)) {
+ Desktop.getDesktop().setOpenFileHandler(openFilesHandler);
+ }
+ if(Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_URI)) {
+ Desktop.getDesktop().setOpenURIHandler(openURIHandler);
+ }
}
} catch(Exception e) {
log.error("Could not add URI handler", e);
diff --git a/src/main/java/com/sparrowwallet/sparrow/MainApp.java b/src/main/java/com/sparrowwallet/sparrow/MainApp.java
index e49c37a9..ce1da249 100644
--- a/src/main/java/com/sparrowwallet/sparrow/MainApp.java
+++ b/src/main/java/com/sparrowwallet/sparrow/MainApp.java
@@ -23,14 +23,15 @@ import org.controlsfx.tools.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
+import tk.pratanumandal.unique4j.Unique4jList;
+import tk.pratanumandal.unique4j.exception.Unique4jException;
import java.io.File;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.util.*;
import java.util.stream.Collectors;
public class MainApp extends Application {
+ public static final String APP_ID = "com.sparrowwallet.sparrow";
public static final String APP_NAME = "Sparrow";
public static final String APP_VERSION = "1.4.0";
public static final String APP_HOME_PROPERTY = "sparrow.home";
@@ -38,8 +39,7 @@ public class MainApp extends Application {
private Stage mainStage;
- private static final List argFiles = new ArrayList<>();
- private static final List argUris = new ArrayList<>();
+ private static SparrowUnique sparrowUnique;
@Override
public void init() throws Exception {
@@ -113,13 +113,7 @@ public class MainApp extends Application {
}
}
- for(File argFile : argFiles) {
- appController.openFile(argFile);
- }
-
- for(URI argUri : argUris) {
- AppServices.handleURI(argUri);
- }
+ AppServices.openFileUriArguments(stage);
AppServices.get().start();
}
@@ -128,6 +122,9 @@ public class MainApp extends Application {
public void stop() throws Exception {
AppServices.get().stop();
mainStage.close();
+ if(sparrowUnique != null) {
+ sparrowUnique.freeLock();
+ }
}
public static void main(String[] argv) {
@@ -175,22 +172,18 @@ public class MainApp extends Application {
getLogger().info("Using " + Network.get() + " configuration");
}
- if(!jCommander.getUnknownOptions().isEmpty()) {
- for(String fileUri : jCommander.getUnknownOptions()) {
+ List fileUriArguments = jCommander.getUnknownOptions();
+ if(!fileUriArguments.isEmpty()) {
+ if(args.network == null && args.dir == null) {
try {
- File file = new File(fileUri);
- if(file.exists()) {
- argFiles.add(file);
- continue;
- }
- URI uri = new URI(fileUri);
- argUris.add(uri);
- } catch(URISyntaxException e) {
- getLogger().warn("Could not parse " + fileUri + " as a valid file or URI");
- } catch(Exception e) {
- //ignore
+ sparrowUnique = new SparrowUnique(APP_ID, fileUriArguments);
+ sparrowUnique.acquireLock(); //Will exit app after sending fileUriArguments if lock cannot be acquired
+ } catch(Unique4jException e) {
+ getLogger().error("Could not obtain unique lock", e);
}
}
+
+ AppServices.parseFileUriArguments(fileUriArguments);
}
SLF4JBridgeHandler.removeHandlersForRootLogger();
@@ -201,4 +194,29 @@ public class MainApp extends Application {
private static Logger getLogger() {
return LoggerFactory.getLogger(MainApp.class);
}
+
+ private static class SparrowUnique extends Unique4jList {
+ private final List fileUriArguments;
+
+ public SparrowUnique(String APP_ID, List fileUriArguments) {
+ super(APP_ID + "." + Network.get());
+ this.fileUriArguments = fileUriArguments;
+ }
+
+ @Override
+ protected void receiveMessageList(List messageList) {
+ AppServices.parseFileUriArguments(messageList);
+ AppServices.openFileUriArguments(null);
+ }
+
+ @Override
+ protected List sendMessageList() {
+ return fileUriArguments;
+ }
+
+ @Override
+ protected void beforeExit() {
+ getLogger().info("Opening files/URIs in already running instance, exiting...");
+ }
+ }
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java
index 88ec5ffa..bb16c0db 100644
--- a/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java
+++ b/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java
@@ -2,17 +2,30 @@ package com.sparrowwallet.sparrow.event;
import javafx.stage.Window;
+import java.io.File;
+
/**
* Event class used to request the transaction open file dialog
*/
public class RequestTransactionOpenEvent {
private final Window window;
+ private final File file;
public RequestTransactionOpenEvent(Window window) {
this.window = window;
+ this.file = null;
+ }
+
+ public RequestTransactionOpenEvent(Window window, File file) {
+ this.window = window;
+ this.file = file;
}
public Window getWindow() {
return window;
}
+
+ public File getFile() {
+ return file;
+ }
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/event/RequestWalletOpenEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/RequestWalletOpenEvent.java
index eedf6fb5..362880e6 100644
--- a/src/main/java/com/sparrowwallet/sparrow/event/RequestWalletOpenEvent.java
+++ b/src/main/java/com/sparrowwallet/sparrow/event/RequestWalletOpenEvent.java
@@ -2,17 +2,30 @@ package com.sparrowwallet.sparrow.event;
import javafx.stage.Window;
+import java.io.File;
+
/**
* Event class used to request the wallet open dialog
*/
public class RequestWalletOpenEvent {
private final Window window;
+ private final File file;
public RequestWalletOpenEvent(Window window) {
this.window = window;
+ this.file = null;
+ }
+
+ public RequestWalletOpenEvent(Window window, File file) {
+ this.window = window;
+ this.file = file;
}
public Window getWindow() {
return window;
}
+
+ public File getFile() {
+ return file;
+ }
}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 34e4085d..4317a529 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -11,7 +11,6 @@ open module com.sparrowwallet.sparrow {
requires com.sparrowwallet.drongo;
requires com.google.common;
requires flowless;
- requires com.google.gson;
requires com.google.zxing;
requires com.google.zxing.javase;
requires simple.json.rpc.client;
@@ -28,6 +27,7 @@ open module com.sparrowwallet.sparrow {
requires bwt.jni;
requires jtorctl;
requires javacsv;
+ requires unique4j;
requires jul.to.slf4j;
requires bridj;
}
\ No newline at end of file