aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--filter.cpp7
-rw-r--r--filter.h1
-rw-r--r--main.cpp41
-rw-r--r--main.qml50
-rw-r--r--monero-wallet-gui.pro10
-rw-r--r--pages/Transfer.qml18
-rw-r--r--share/Info.plist20
-rw-r--r--src/qt/ipc.cpp106
-rw-r--r--src/qt/ipc.h35
-rw-r--r--src/qt/mime.cpp42
-rw-r--r--src/qt/mime.h8
-rw-r--r--src/qt/utils.cpp20
-rw-r--r--src/qt/utils.h11
13 files changed, 354 insertions, 15 deletions
diff --git a/filter.cpp b/filter.cpp
index e66e5ade..581d0750 100644
--- a/filter.cpp
+++ b/filter.cpp
@@ -43,6 +43,13 @@ filter::filter(QObject *parent) :
}
bool filter::eventFilter(QObject *obj, QEvent *ev) {
+ // macOS sends fileopen signal for incoming uri handlers
+ if (ev->type() == QEvent::FileOpen) {
+ QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(ev);
+ QUrl scheme = openEvent->url();
+ emit uriHandler(scheme);
+ }
+
if(ev->type() == QEvent::KeyPress || ev->type() == QEvent::MouseButtonRelease){
emit userActivity();
}
diff --git a/filter.h b/filter.h
index bb3a78b7..b83926a3 100644
--- a/filter.h
+++ b/filter.h
@@ -49,6 +49,7 @@ signals:
void mousePressed(const QVariant &o, const QVariant &x, const QVariant &y);
void mouseReleased(const QVariant &o, const QVariant &x, const QVariant &y);
void userActivity();
+ void uriHandler(const QUrl &url);
};
#endif // FILTER_H
diff --git a/main.cpp b/main.cpp
index eca65333..8ec59e2b 100644
--- a/main.cpp
+++ b/main.cpp
@@ -32,6 +32,7 @@
#include <QStandardPaths>
#include <QIcon>
#include <QDebug>
+#include <QDesktopServices>
#include <QObject>
#include <QDesktopWidget>
#include <QScreen>
@@ -60,6 +61,9 @@
#include "wallet/api/wallet2_api.h"
#include "Logger.h"
#include "MainApp.h"
+#include "qt/ipc.h"
+#include "qt/utils.h"
+#include "qt/mime.h"
// IOS exclusions
#ifndef Q_OS_IOS
@@ -82,6 +86,8 @@ int main(int argc, char *argv[])
// platform dependant settings
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
bool isDesktop = true;
+#elif defined(Q_OS_LINUX)
+ bool isLinux = true;
#elif defined(Q_OS_ANDROID)
bool isAndroid = true;
#elif defined(Q_OS_IOS)
@@ -127,6 +133,7 @@ int main(int argc, char *argv[])
QCommandLineOption logPathOption(QStringList() << "l" << "log-file",
QCoreApplication::translate("main", "Log to specified file"),
QCoreApplication::translate("main", "file"));
+
parser.addOption(logPathOption);
parser.addHelpOption();
parser.process(app);
@@ -138,11 +145,33 @@ int main(int argc, char *argv[])
Monero::Wallet::init(argv[0], "monero-wallet-gui", logPath.toStdString().c_str(), true);
qInstallMessageHandler(messageHandler);
+ // Get default account name
+ QString accountName = getAccountName();
// loglevel is configured in main.qml. Anything lower than
// qWarning is not shown here.
qWarning().noquote() << "app startd" << "(log: " + logPath + ")";
+#ifdef Q_OS_LINUX
+ registerXdgMime(app);
+#endif
+
+ IPC *ipc = new IPC(&app);
+ QStringList posArgs = parser.positionalArguments();
+
+ for(int i = 0; i != posArgs.count(); i++){
+ QString arg = QString(posArgs.at(i));
+ if(arg.isEmpty() || arg.length() >= 512) continue;
+ if(arg.contains(reURI)){
+ if(!ipc->saveCommand(arg)){
+ return 0;
+ }
+ }
+ }
+
+ // start listening
+ QTimer::singleShot(0, ipc, SLOT(bind()));
+
// screen settings
// Mobile is designed on 128dpi
qreal ref_dpi = 128;
@@ -252,6 +281,8 @@ int main(int argc, char *argv[])
engine.rootContext()->setContextProperty("mainApp", &app);
+ engine.rootContext()->setContextProperty("IPC", ipc);
+
engine.rootContext()->setContextProperty("qtRuntimeVersion", qVersion());
engine.rootContext()->setContextProperty("walletLogPath", logPath);
@@ -295,14 +326,6 @@ int main(int argc, char *argv[])
engine.rootContext()->setContextProperty("moneroAccountsDir", moneroAccountsDir);
}
-
- // Get default account name
- QString accountName = qgetenv("USER"); // mac/linux
- if (accountName.isEmpty())
- accountName = qgetenv("USERNAME"); // Windows
- if (accountName.isEmpty())
- accountName = "My monero Account";
-
engine.rootContext()->setContextProperty("defaultAccountName", accountName);
engine.rootContext()->setContextProperty("applicationDirectory", QApplication::applicationDirPath());
engine.rootContext()->setContextProperty("idealThreadCount", QThread::idealThreadCount());
@@ -312,7 +335,6 @@ int main(int argc, char *argv[])
builtWithScanner = true;
#endif
engine.rootContext()->setContextProperty("builtWithScanner", builtWithScanner);
-
// Load main window (context properties needs to be defined obove this line)
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
if (engine.rootObjects().isEmpty())
@@ -345,5 +367,6 @@ int main(int argc, char *argv[])
QObject::connect(eventFilter, SIGNAL(mousePressed(QVariant,QVariant,QVariant)), rootObject, SLOT(mousePressed(QVariant,QVariant,QVariant)));
QObject::connect(eventFilter, SIGNAL(mouseReleased(QVariant,QVariant,QVariant)), rootObject, SLOT(mouseReleased(QVariant,QVariant,QVariant)));
QObject::connect(eventFilter, SIGNAL(userActivity()), rootObject, SLOT(userActivity()));
+ QObject::connect(eventFilter, SIGNAL(uriHandler(QUrl)), ipc, SLOT(parseCommand(QUrl)));
return app.exec();
}
diff --git a/main.qml b/main.qml
index 94c4a0d1..9776269d 100644
--- a/main.qml
+++ b/main.qml
@@ -399,6 +399,49 @@ ApplicationWindow {
leftPanel.balanceLabelText = qsTr("Balance (#%1%2)").arg(currentWallet.currentSubaddressAccount).arg(accountLabel === "" ? "" : (" – " + accountLabel));
}
+ function onUriHandler(uri){
+ if(uri.startsWith("monero://")){
+ var address = uri.substring("monero://".length);
+
+ var params = {}
+ if(address.length === 0) return;
+ var spl = address.split("?");
+
+ if(spl.length > 2) return;
+ if(spl.length >= 1) {
+ // parse additional params
+ address = spl[0];
+
+ if(spl.length === 2){
+ spl.shift();
+ var item = spl[0];
+
+ var _spl = item.split("&");
+ for (var param in _spl){
+ var _item = _spl[param];
+ if(!_item.indexOf("=") > 0) continue;
+
+ var __spl = _item.split("=");
+ if(__spl.length !== 2) continue;
+
+ params[__spl[0]] = __spl[1];
+ }
+ }
+ }
+
+ // Fill fields
+ middlePanel.transferView.sendTo(address, params["tx_payment_id"], params["tx_description"], params["tx_amount"]);
+
+ // Raise window
+ appWindow.raise();
+ appWindow.show();
+
+ // @TODO: remove after paymentID deprecation
+ if(params.hasOwnProperty("tx_payment_id"))
+ persistentSettings.showPid = true;
+ }
+ }
+
function onWalletConnectionStatusChanged(status){
console.log("Wallet connection status changed " + status)
middlePanel.updateStatus();
@@ -489,6 +532,12 @@ ApplicationWindow {
// Force switch normal view
rootItem.state = "normal";
+
+ // Process queued IPC command
+ if(typeof IPC !== "undefined" && IPC.queuedCmd().length > 0){
+ var queuedCmd = IPC.queuedCmd();
+ if(/^\w+:\/\/(.*)$/.test(queuedCmd)) appWindow.onUriHandler(queuedCmd); // uri
+ }
}
function onWalletClosed(walletAddress) {
@@ -1086,6 +1135,7 @@ ApplicationWindow {
walletManager.deviceButtonPressed.connect(onDeviceButtonPressed);
walletManager.checkUpdatesComplete.connect(onWalletCheckUpdatesComplete);
walletManager.walletPassphraseNeeded.connect(onWalletPassphraseNeeded);
+ IPC.uriHandler.connect(onUriHandler);
if(typeof daemonManager != "undefined") {
daemonManager.daemonStarted.connect(onDaemonStarted);
diff --git a/monero-wallet-gui.pro b/monero-wallet-gui.pro
index 06049f08..5deb5964 100644
--- a/monero-wallet-gui.pro
+++ b/monero-wallet-gui.pro
@@ -61,7 +61,10 @@ HEADERS += \
src/zxcvbn-c/zxcvbn.h \
src/libwalletqt/UnsignedTransaction.h \
Logger.h \
- MainApp.h
+ MainApp.h \
+ src/qt/ipc.h \
+ src/qt/mime.h \
+ src/qt/utils.h
SOURCES += main.cpp \
filter.cpp \
@@ -89,7 +92,10 @@ SOURCES += main.cpp \
src/zxcvbn-c/zxcvbn.c \
src/libwalletqt/UnsignedTransaction.cpp \
Logger.cpp \
- MainApp.cpp
+ MainApp.cpp \
+ src/qt/ipc.cpp \
+ src/qt/mime.cpp \
+ src/qt/utils.cpp
CONFIG(DISABLE_PASS_STRENGTH_METER) {
HEADERS -= src/zxcvbn-c/zxcvbn.h
diff --git a/pages/Transfer.qml b/pages/Transfer.qml
index 6a8ea1be..09b3df58 100644
--- a/pages/Transfer.qml
+++ b/pages/Transfer.qml
@@ -708,10 +708,20 @@ Rectangle {
}
// Popuplate fields from addressbook.
- function sendTo(address, paymentId, description){
- addressLine.text = address
- setPaymentId(paymentId);
- setDescription(description);
+ function sendTo(address, paymentId, description, amount){
+ middlePanel.state = 'Transfer';
+
+ if(typeof address !== 'undefined')
+ addressLine.text = address
+
+ if(typeof paymentId !== 'undefined')
+ setPaymentId(paymentId);
+
+ if(typeof description !== 'undefined')
+ setDescription(description);
+
+ if(typeof amount !== 'undefined')
+ amountLine.text = amount;
}
function updateSendButton(){
diff --git a/share/Info.plist b/share/Info.plist
index 5b7a0b3f..438443d2 100644
--- a/share/Info.plist
+++ b/share/Info.plist
@@ -31,5 +31,25 @@
<key>NSRequiresAquaSystemAppearance</key>
<string>True</string>
+
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>monero Handler</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>monero</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>moneroseed Handler</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>moneroseed</string>
+ </array>
+ </dict>
+ </array>
</dict>
</plist>
diff --git a/src/qt/ipc.cpp b/src/qt/ipc.cpp
new file mode 100644
index 00000000..c7d1aa86
--- /dev/null
+++ b/src/qt/ipc.cpp
@@ -0,0 +1,106 @@
+#include <QCoreApplication>
+#include <QLocalSocket>
+#include <QLocalServer>
+#include <QtNetwork>
+#include <QDebug>
+
+#include "ipc.h"
+#include "utils.h"
+
+// Start listening for incoming IPC commands on UDS (Unix) or named pipe (Windows)
+void IPC::bind(){
+ QString path = QString(this->m_socketFile.absoluteFilePath());
+ qDebug() << path;
+
+ this->m_server = new QLocalServer(this);
+ this->m_server->setSocketOptions(QLocalServer::UserAccessOption);
+
+ bool restarted = false;
+ if(!this->m_server->listen(path)){
+ // On Unix if the server crashes without closing listen will fail with AddressInUseError.
+ // To create a new server the file should be removed. On Windows two local servers can listen
+ // to the same pipe at the same time, but any connections will go to one of the server.
+#ifdef Q_OS_UNIX
+ qDebug() << QString("Unable to start IPC server in \"%1\": \"%2\". Retrying.").arg(path).arg(this->m_server->errorString());
+ if(this->m_socketFile.exists()){
+ QFile file(path);
+ file.remove();
+
+ if(this->m_server->listen(path)){
+ restarted = true;
+ }
+ }
+#endif
+ if(!restarted)
+ qDebug() << QString("Unable to start IPC server in \"%1\": \"%2\".").arg(path).arg(this->m_server->errorString());
+ }
+
+ connect(this->m_server, &QLocalServer::newConnection, this, &IPC::handleConnection);
+}
+
+// Process incoming IPC command. First check if monero-wallet-gui is
+// already running. If it is, send it to that instance instead, if not,
+// queue the command for later use inside our QML engine. Returns true
+// when queued, false if sent to another instance, at which point we can
+// kill the current process.
+bool IPC::saveCommand(QString cmdString){
+ qDebug() << QString("saveCommand called: %1").arg(cmdString);
+
+ QLocalSocket ls;
+ QByteArray buffer;
+ buffer = buffer.append(cmdString);
+ QString socketFilePath = this->socketFile().filePath();
+
+ ls.connectToServer(socketFilePath, QIODevice::WriteOnly);
+ if(ls.waitForConnected(1000)){
+ ls.write(buffer);
+ if (!ls.waitForBytesWritten(1000)){
+ qDebug() << QString("Could not send command \"%1\" over IPC %2: \"%3\"").arg(cmdString, socketFilePath, ls.errorString());
+ return false;
+ }
+
+ qDebug() << QString("Sent command \"%1\" over IPC \"%2\"").arg(cmdString, socketFilePath);
+ return false;
+ }
+
+ if(ls.isOpen())
+ ls.disconnectFromServer();
+
+ // Queue for later
+ this->SetQueuedCmd(cmdString);
+ return true;
+}
+
+bool IPC::saveCommand(const QUrl &url){;
+ this->saveCommand(url.toString());
+}
+
+void IPC::handleConnection(){
+ QLocalSocket *clientConnection = this->m_server->nextPendingConnection();
+ connect(clientConnection, &QLocalSocket::disconnected,
+ clientConnection, &QLocalSocket::deleteLater);
+
+ clientConnection->waitForReadyRead(2);
+ QByteArray cmdArray = clientConnection->readAll();
+ QString cmdString = QTextCodec::codecForMib(106)->toUnicode(cmdArray); // UTF-8
+ qDebug() << cmdString;
+
+ this->parseCommand(cmdString);
+
+ clientConnection->close();
+ delete clientConnection;
+}
+
+void IPC::parseCommand(const QUrl &url){
+ this->parseCommand(url.toString());
+}
+
+void IPC::parseCommand(QString cmdString){
+ if(cmdString.contains(reURI)){
+ this->emitUriHandler(cmdString);
+ }
+}
+
+void IPC::emitUriHandler(QString uriString){
+ emit uriHandler(uriString);
+}
diff --git a/src/qt/ipc.h b/src/qt/ipc.h
new file mode 100644
index 00000000..5c3cc73a
--- /dev/null
+++ b/src/qt/ipc.h
@@ -0,0 +1,35 @@
+#ifndef IPC_H
+#define IPC_H
+
+#include <QtCore>
+#include <QLocalServer>
+#include <qt/utils.h>
+
+class IPC : public QObject
+{
+Q_OBJECT
+public:
+ IPC(QObject *parent = 0) : QObject(parent) {}
+ QFileInfo socketFile() const { return m_socketFile; }
+ Q_INVOKABLE QString queuedCmd() { return m_queuedCmd; }
+ void SetQueuedCmd(const QString cmdString) { m_queuedCmd = cmdString; }
+
+public slots:
+ void bind();
+ void handleConnection();
+ bool saveCommand(QString cmdString);
+ bool saveCommand(const QUrl &url);
+ void parseCommand(QString cmdString);
+ void parseCommand(const QUrl &url);
+ void emitUriHandler(QString uriString);
+
+signals:
+ void uriHandler(QString uriString);
+
+private:
+ QLocalServer *m_server;
+ QString m_queuedCmd;
+ QFileInfo m_socketFile = QFileInfo(QString(QDir::tempPath() + "/xmr-gui_%2.sock").arg(getAccountName()));
+};
+
+#endif // IPC_H
diff --git a/src/qt/mime.cpp b/src/qt/mime.cpp
new file mode 100644
index 00000000..76fbb19c
--- /dev/null
+++ b/src/qt/mime.cpp
@@ -0,0 +1,42 @@
+#include <QtCore>
+#include <QApplication>
+#include <QFile>
+#include <QTextStream>
+
+#include "mime.h"
+#include "utils.h"
+
+void registerXdgMime(QApplication &app){
+ // MacOS handled via Info.plist
+ // Windows handled in the installer by rbrunner7
+
+ QString xdg = QString(
+ "[Desktop Entry]\n"
+ "Name=Monero GUI\n"
+ "GenericName=Monero-GUI\n"
+ "X-GNOME-FullName=Monero-GUI\n"
+ "Comment=Monero GUI\n"
+ "Keywords=Monero;\n"
+ "Exec=%1 %u\n"
+ "Terminal=false\n"
+ "Type=Application\n"
+ "Icon=monero\n"
+ "Categories=Network;GNOME;Qt;\n"
+ "MimeType=x-scheme-handler/monero;x-scheme-handler/moneroseed\n"
+ "StartupNotify=true\n"
+ "X-GNOME-Bugzilla-Bugzilla=GNOME\n"
+ "X-GNOME-UsesNotifications=true\n"
+ ).arg(app.applicationFilePath());
+
+ QString appPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
+ QString filePath = QString("%1/monero-gui.desktop").arg(appPath);
+
+ qDebug() << QString("Writing %1").arg(filePath);
+ QFile file(filePath);
+ if(file.open(QIODevice::WriteOnly)){
+ QTextStream out(&file); out << xdg << endl;
+ file.close();
+ }
+ else
+ file.close();
+}
diff --git a/src/qt/mime.h b/src/qt/mime.h
new file mode 100644
index 00000000..a6c79587
--- /dev/null
+++ b/src/qt/mime.h
@@ -0,0 +1,8 @@
+#ifndef MIME_H
+#define MIME_H
+
+#include <QApplication>
+
+void registerXdgMime(QApplication &app);
+
+#endif // MIME_H
diff --git a/src/qt/utils.cpp b/src/qt/utils.cpp
new file mode 100644
index 00000000..cb0ad4f9
--- /dev/null
+++ b/src/qt/utils.cpp
@@ -0,0 +1,20 @@
+#include <QtCore>
+
+#include "utils.h"
+
+bool fileExists(QString path) {
+ QFileInfo check_file(path);
+ if (check_file.exists() && check_file.isFile())
+ return true;
+ else
+ return false;
+}
+
+QString getAccountName(){
+ QString accountName = qgetenv("USER"); // mac/linux
+ if (accountName.isEmpty())
+ accountName = qgetenv("USERNAME"); // Windows
+ if (accountName.isEmpty())
+ accountName = "My monero Account";
+ return accountName;
+}
diff --git a/src/qt/utils.h b/src/qt/utils.h
new file mode 100644
index 00000000..6a014ffc
--- /dev/null
+++ b/src/qt/utils.h
@@ -0,0 +1,11 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <QtCore>
+#include <QRegExp>
+
+bool fileExists(QString path);
+QString getAccountName();
+const static QRegExp reURI = QRegExp("^\\w+:\\/\\/([\\w+\\-?\\-_\\-=\\-&]+)");
+
+#endif // UTILS_H