mirror of
https://github.com/monero-project/monero-gui
synced 2024-12-22 03:15:52 +01:00
IPC and custom protocol handler for monero://
This commit is contained in:
parent
ff6ce6294b
commit
18f2accc7f
@ -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();
|
||||
}
|
||||
|
1
filter.h
1
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
|
||||
|
41
main.cpp
41
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();
|
||||
}
|
||||
|
50
main.qml
50
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);
|
||||
|
@ -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
|
||||
|
@ -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(){
|
||||
|
@ -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>
|
||||
|
106
src/qt/ipc.cpp
Normal file
106
src/qt/ipc.cpp
Normal file
@ -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);
|
||||
}
|
35
src/qt/ipc.h
Normal file
35
src/qt/ipc.h
Normal file
@ -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
|
42
src/qt/mime.cpp
Normal file
42
src/qt/mime.cpp
Normal file
@ -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();
|
||||
}
|
8
src/qt/mime.h
Normal file
8
src/qt/mime.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef MIME_H
|
||||
#define MIME_H
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
void registerXdgMime(QApplication &app);
|
||||
|
||||
#endif // MIME_H
|
20
src/qt/utils.cpp
Normal file
20
src/qt/utils.cpp
Normal file
@ -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;
|
||||
}
|
11
src/qt/utils.h
Normal file
11
src/qt/utils.h
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user