Skip to content

Commit

Permalink
IPC and custom protocol handler for monero://
Browse files Browse the repository at this point in the history
  • Loading branch information
sanderfoobar committed Mar 22, 2019
1 parent 02e1663 commit 557294f
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 15 deletions.
36 changes: 28 additions & 8 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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
Expand Down Expand Up @@ -119,7 +122,11 @@ int main(int argc, char *argv[])
QCommandLineOption logPathOption(QStringList() << "l" << "log-file",
QCoreApplication::translate("main", "Log to specified file"),
QCoreApplication::translate("main", "file"));
QCommandLineOption ipcOption(QStringList() << "i" << "ipc",
QCoreApplication::translate("main", "IPC for internal usage"),
QCoreApplication::translate("main", "ipc"));
parser.addOption(logPathOption);
parser.addOption(ipcOption);
parser.addHelpOption();
parser.process(app);

Expand All @@ -130,11 +137,29 @@ 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 + ")";

// Register custom protocol handler(s)
registerMime(app);

// IPC; check incoming command
IPC *ipc = new IPC(&app);
const QString cmdString = parser.value(ipcOption);
if(!cmdString.isEmpty()){
// Kill process if monero-wallet-gui is already running
if(!ipc->saveCommand(cmdString)){
return 0;
}
}

// IPC; start listening
QTimer::singleShot(0, ipc, SLOT(bind()));

// screen settings
// Mobile is designed on 128dpi
qreal ref_dpi = 128;
Expand Down Expand Up @@ -244,6 +269,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);
Expand Down Expand Up @@ -290,14 +317,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());
Expand Down Expand Up @@ -340,5 +359,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(ipc, SIGNAL(uriHandler(QString)), rootObject, SLOT(uriHandler(QString)));
return app.exec();
}
28 changes: 27 additions & 1 deletion main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,26 @@ 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);
if(address.length === 0) return;

var tx_amount;
var has_tx_amount = address.indexOf("tx_amount");
if(has_tx_amount > 0){
tx_amount = address.substring(96 + "tx_amount=".length);
address = address.substring(0, 96);
}

if(address.length === 0) return;
middlePanel.transferView.sendTo(address, undefined, undefined, tx_amount);

appWindow.raise();
appWindow.show();
}
}

function onWalletConnectionStatusChanged(status){
console.log("Wallet connection status changed " + status)
middlePanel.updateStatus();
Expand Down Expand Up @@ -449,6 +469,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
}
}


Expand Down Expand Up @@ -1001,7 +1027,7 @@ ApplicationWindow {
walletManager.walletOpened.connect(onWalletOpened);
walletManager.walletClosed.connect(onWalletClosed);
walletManager.checkUpdatesComplete.connect(onWalletCheckUpdatesComplete);

IPC.uriHandler.connect(onUriHandler);
if(typeof daemonManager != "undefined") {
daemonManager.daemonStarted.connect(onDaemonStarted);
daemonManager.daemonStartFailure.connect(onDaemonStartFailure);
Expand Down
10 changes: 8 additions & 2 deletions monero-wallet-gui.pro
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,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 \
Expand Down Expand Up @@ -83,7 +86,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
Expand Down
18 changes: 14 additions & 4 deletions pages/Transfer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -712,9 +712,19 @@ 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;
}
}
98 changes: 98 additions & 0 deletions src/qt/ipc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#include <sys/socket.h>

#include <QCoreApplication>
#include <QLocalSocket>
#include <QLocalServer>
#include <QtNetwork>
#include <QDebug>

#include "ipc.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){
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;
}

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(QString cmdString){
// Detect URI string
if(cmdString.contains(QRegExp("^\\w+:\\/\\/(.*)$"))){
this->emitUriHandler(cmdString);
}
}

void IPC::emitUriHandler(QString uriString){
emit uriHandler(uriString);
}
33 changes: 33 additions & 0 deletions src/qt/ipc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#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);
void parseCommand(QString cmdString);
void emitUriHandler(QString uriString);

signals:
void uriHandler(QString uriString);

private:
QLocalServer *m_server;
QString m_queuedCmd;
QFileInfo m_socketFile = QFileInfo(QString(QDir::tempPath() + "/monero-wallet-gui_%2.sock").arg(getAccountName()));
};

#endif // IPC_H
41 changes: 41 additions & 0 deletions src/qt/mime.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <QtCore>
#include <QApplication>
#include <QFile>
#include <QTextStream>

#include "mime.h"
#include "utils.h"

void registerMime(QApplication &app){
#ifdef Q_OS_LINUX
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 --ipc %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();
#endif
}
8 changes: 8 additions & 0 deletions src/qt/mime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef MIME_H
#define MIME_H

#include <QApplication>

void registerMime(QApplication &app);

#endif // MIME_H
21 changes: 21 additions & 0 deletions src/qt/utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <QtCore>
#include "utils.h"

bool fileExists(QString path) {
QFileInfo check_file(path);
// check if file exists and if yes: Is it really a file and no directory?
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;
}
10 changes: 10 additions & 0 deletions src/qt/utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef UTILS_H
#define UTILS_H

#include <QtCore>


bool fileExists(QString path);
QString getAccountName();

#endif // UTILS_H

0 comments on commit 557294f

Please sign in to comment.