-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reimplement click-to-pay links. Add OSX support.
Switch to using Qt's QLocalServer/QLocalSocket to handle bitcoin payment links (bitcoin:... URIs) Reason for switch: the boost::interprocess mechanism seemed flaky, and doesn't mesh as well with "The Qt Way" qtipcserver.cpp/h is replaced by paymentserver.cpp/h Click-to-pay now also works on OSX, with a custom Info.plist that registers Bitcoin-Qt as a handler for bitcoin: URLs and an event listener on the main QApplication that handles QFileOpenEvents (Qt translates 'url clicked' AppleEvents into QFileOpenEvents automagically).
- Loading branch information
1 parent
2f0fa79
commit 8269a09
Showing
7 changed files
with
272 additions
and
204 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"> | ||
<plist version="0.9"> | ||
<dict> | ||
<key>CFBundleIconFile</key> | ||
<string>bitcoin.icns</string> | ||
<key>CFBundlePackageType</key> | ||
<string>APPL</string> | ||
<key>CFBundleGetInfoString</key> | ||
<string>Bitcoin-Qt</string> | ||
<key>CFBundleSignature</key> | ||
<string>????</string> | ||
<key>CFBundleExecutable</key> | ||
<string>Bitcoin-Qt</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>org.bitcoinfoundation.Bitcoin-Qt</string> | ||
<key>CFBundleURLTypes</key> | ||
<array> | ||
<dict> | ||
<key>CFBundleTypeRole</key> | ||
<string>Editor</string> | ||
<key>CFBundleURLName</key> | ||
<string>org.bitcoinfoundation.BitcoinPayment</string> | ||
<key>CFBundleURLSchemes</key> | ||
<array> | ||
<string>bitcoin</string> | ||
</array> | ||
</dict> | ||
</array> | ||
</dict> | ||
</plist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// 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 "paymentserver.h" | ||
#include "guiconstants.h" | ||
#include "ui_interface.h" | ||
#include "util.h" | ||
|
||
#include <QApplication> | ||
#include <QByteArray> | ||
#include <QCoreApplication> | ||
#include <QDataStream> | ||
#include <QDebug> | ||
#include <QFileOpenEvent> | ||
#include <QHash> | ||
#include <QLocalServer> | ||
#include <QLocalSocket> | ||
#include <QStringList> | ||
#include <QUrl> | ||
|
||
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 = QCoreApplication::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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
#ifndef PAYMENTSERVER_H | ||
#define PAYMENTSERVER_H | ||
|
||
// | ||
// This class handles payment requests from clicking on | ||
// bitcoin: URIs | ||
// | ||
// This is somewhat tricky, because we have to deal with | ||
// the situation where the user clicks on a link during | ||
// startup/initialization, when the splash-screen is up | ||
// but the main window (and the Send Coins tab) is not. | ||
// | ||
// So, the strategy is: | ||
// | ||
// Create the server, and register the event handler, | ||
// when the application is created. Save any URIs | ||
// received at or during startup in a list. | ||
// | ||
// When startup is finished and the main window is | ||
// show, a signal is sent to slot uiReady(), which | ||
// emits a receivedURL() signal for any payment | ||
// requests that happened during startup. | ||
// | ||
// After startup, receivedURL() happens as usual. | ||
// | ||
// This class has one more feature: a static | ||
// method that finds URIs passed in the command line | ||
// and, if a server is running in another process, | ||
// sends them to the server. | ||
// | ||
#include <QObject> | ||
#include <QString> | ||
|
||
class QApplication; | ||
class QLocalServer; | ||
|
||
class PaymentServer : public QObject | ||
{ | ||
Q_OBJECT | ||
private: | ||
bool saveURIs; | ||
QLocalServer* uriServer; | ||
|
||
public: | ||
// Returns true if there were URIs on the command line | ||
// which were successfully sent to an already-running | ||
// process. | ||
static bool ipcSendCommandLine(); | ||
|
||
PaymentServer(QApplication* parent); | ||
|
||
bool eventFilter(QObject *object, QEvent *event); | ||
|
||
signals: | ||
void receivedURI(QString); | ||
|
||
public slots: | ||
// Signal this when the main window's UI is ready | ||
// to display payment requests to the user | ||
void uiReady(); | ||
|
||
private slots: | ||
void handleURIConnection(); | ||
}; | ||
|
||
#endif // PAYMENTSERVER_H |
Oops, something went wrong.