|
|
|
// Copyright (c) 2009-2012 The Bitcoin developers
|
|
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
|
|
|
#include "paymentserver.h"
|
|
|
|
|
|
|
|
#include "guiconstants.h"
|
|
|
|
#include "ui_interface.h"
|
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
#include <QByteArray>
|
|
|
|
#include <QDataStream>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QFileOpenEvent>
|
|
|
|
#include <QHash>
|
|
|
|
#include <QLocalServer>
|
|
|
|
#include <QLocalSocket>
|
|
|
|
#include <QStringList>
|
|
|
|
#if QT_VERSION < 0x050000
|
|
|
|
#include <QUrl>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
using namespace boost;
|
|
|
|
|
|
|
|
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
|
|
|
|
const QString BITCOIN_IPC_PREFIX("bitcoin:");
|
|
|
|
|
|
|
|
//
|
|
|
|
// Create a name that is unique for:
|
|
|
|
// testnet / non-testnet
|
|
|
|
// data directory
|
|
|
|
//
|
|
|
|
static QString ipcServerName()
|
|
|
|
{
|
|
|
|
QString name("BitcoinQt");
|
|
|
|
|
|
|
|
// Append a simple hash of the datadir
|
|
|
|
// Note that GetDataDir(true) returns a different path
|
|
|
|
// for -testnet versus main net
|
|
|
|
QString ddir(GetDataDir(true).string().c_str());
|
|
|
|
name.append(QString::number(qHash(ddir)));
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// This stores payment requests received before
|
|
|
|
// the main GUI window is up and ready to ask the user
|
|
|
|
// to send payment.
|
|
|
|
//
|
|
|
|
static QStringList savedPaymentRequests;
|
|
|
|
|
|
|
|
//
|
|
|
|
// Sending to the server is done synchronously, at startup.
|
|
|
|
// If the server isn't already running, startup continues,
|
|
|
|
// and the items in savedPaymentRequest will be handled
|
|
|
|
// when uiReady() is called.
|
|
|
|
//
|
|
|
|
bool PaymentServer::ipcSendCommandLine()
|
|
|
|
{
|
|
|
|
bool fResult = false;
|
|
|
|
|
|
|
|
const QStringList& args = qApp->arguments();
|
|
|
|
for (int i = 1; i < args.size(); i++)
|
|
|
|
{
|
|
|
|
if (!args[i].startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive))
|
|
|
|
continue;
|
|
|
|
savedPaymentRequests.append(args[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (const QString& arg, savedPaymentRequests)
|
|
|
|
{
|
|
|
|
QLocalSocket* socket = new QLocalSocket();
|
|
|
|
socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
|
|
|
|
if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
QByteArray block;
|
|
|
|
QDataStream out(&block, QIODevice::WriteOnly);
|
|
|
|
out.setVersion(QDataStream::Qt_4_0);
|
|
|
|
out << arg;
|
|
|
|
out.device()->seek(0);
|
|
|
|
socket->write(block);
|
|
|
|
socket->flush();
|
|
|
|
|
|
|
|
socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
|
|
|
|
socket->disconnectFromServer();
|
|
|
|
delete socket;
|
|
|
|
fResult = true;
|
|
|
|
}
|
|
|
|
return fResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
PaymentServer::PaymentServer(QApplication* parent) : QObject(parent), saveURIs(true)
|
|
|
|
{
|
|
|
|
// Install global event filter to catch QFileOpenEvents on the mac (sent when you click bitcoin: links)
|
|
|
|
parent->installEventFilter(this);
|
|
|
|
|
|
|
|
QString name = ipcServerName();
|
|
|
|
|
|
|
|
// Clean up old socket leftover from a crash:
|
|
|
|
QLocalServer::removeServer(name);
|
|
|
|
|
|
|
|
uriServer = new QLocalServer(this);
|
|
|
|
|
|
|
|
if (!uriServer->listen(name))
|
|
|
|
qDebug() << tr("Cannot start bitcoin: click-to-pay handler");
|
|
|
|
else
|
|
|
|
connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection()));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PaymentServer::eventFilter(QObject *object, QEvent *event)
|
|
|
|
{
|
|
|
|
// clicking on bitcoin: URLs creates FileOpen events on the Mac:
|
|
|
|
if (event->type() == QEvent::FileOpen)
|
|
|
|
{
|
|
|
|
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
|
|
|
|
if (!fileEvent->url().isEmpty())
|
|
|
|
{
|
|
|
|
if (saveURIs) // Before main window is ready:
|
|
|
|
savedPaymentRequests.append(fileEvent->url().toString());
|
|
|
|
else
|
|
|
|
emit receivedURI(fileEvent->url().toString());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PaymentServer::uiReady()
|
|
|
|
{
|
|
|
|
saveURIs = false;
|
|
|
|
foreach (const QString& s, savedPaymentRequests)
|
|
|
|
emit receivedURI(s);
|
|
|
|
savedPaymentRequests.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PaymentServer::handleURIConnection()
|
|
|
|
{
|
|
|
|
QLocalSocket *clientConnection = uriServer->nextPendingConnection();
|
|
|
|
|
|
|
|
while (clientConnection->bytesAvailable() < (int)sizeof(quint32))
|
|
|
|
clientConnection->waitForReadyRead();
|
|
|
|
|
|
|
|
connect(clientConnection, SIGNAL(disconnected()),
|
|
|
|
clientConnection, SLOT(deleteLater()));
|
|
|
|
|
|
|
|
QDataStream in(clientConnection);
|
|
|
|
in.setVersion(QDataStream::Qt_4_0);
|
|
|
|
if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QString message;
|
|
|
|
in >> message;
|
|
|
|
|
|
|
|
if (saveURIs)
|
|
|
|
savedPaymentRequests.append(message);
|
|
|
|
else
|
|
|
|
emit receivedURI(message);
|
|
|
|
}
|