Browse Source
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).miguelfreitas
Gavin Andresen
12 years ago
7 changed files with 272 additions and 204 deletions
@ -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> |
@ -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); |
||||||
|
} |
@ -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
|
@ -1,165 +0,0 @@ |
|||||||
// 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 <boost/version.hpp> |
|
||||||
#if defined(WIN32) && BOOST_VERSION == 104900 |
|
||||||
#define BOOST_INTERPROCESS_HAS_WINDOWS_KERNEL_BOOTTIME |
|
||||||
#define BOOST_INTERPROCESS_HAS_KERNEL_BOOTTIME |
|
||||||
#endif |
|
||||||
|
|
||||||
#include "qtipcserver.h" |
|
||||||
#include "guiconstants.h" |
|
||||||
#include "ui_interface.h" |
|
||||||
#include "util.h" |
|
||||||
|
|
||||||
#include <boost/algorithm/string/predicate.hpp> |
|
||||||
#include <boost/date_time/posix_time/posix_time.hpp> |
|
||||||
#include <boost/interprocess/ipc/message_queue.hpp> |
|
||||||
#include <boost/version.hpp> |
|
||||||
|
|
||||||
#if defined(WIN32) && (!defined(BOOST_INTERPROCESS_HAS_WINDOWS_KERNEL_BOOTTIME) || !defined(BOOST_INTERPROCESS_HAS_KERNEL_BOOTTIME) || BOOST_VERSION < 104900) |
|
||||||
#warning Compiling without BOOST_INTERPROCESS_HAS_WINDOWS_KERNEL_BOOTTIME and BOOST_INTERPROCESS_HAS_KERNEL_BOOTTIME uncommented in boost/interprocess/detail/tmp_dir_helpers.hpp or using a boost version before 1.49 may have unintended results see svn.boost.org/trac/boost/ticket/5392 |
|
||||||
#endif |
|
||||||
|
|
||||||
using namespace boost; |
|
||||||
using namespace boost::interprocess; |
|
||||||
using namespace boost::posix_time; |
|
||||||
|
|
||||||
// holds Bitcoin-Qt message queue name (initialized in bitcoin.cpp)
|
|
||||||
std::string strBitcoinURIQueueName; |
|
||||||
|
|
||||||
#if defined MAC_OSX || defined __FreeBSD__ |
|
||||||
// URI handling not implemented on OSX yet
|
|
||||||
|
|
||||||
void ipcScanRelay(int argc, char *argv[]) { } |
|
||||||
void ipcInit(int argc, char *argv[]) { } |
|
||||||
|
|
||||||
#else |
|
||||||
|
|
||||||
static void ipcThread2(void* pArg); |
|
||||||
|
|
||||||
static bool ipcScanCmd(int argc, char *argv[], bool fRelay) |
|
||||||
{ |
|
||||||
// Check for URI in argv
|
|
||||||
bool fSent = false; |
|
||||||
for (int i = 1; i < argc; i++) |
|
||||||
{ |
|
||||||
if (boost::algorithm::istarts_with(argv[i], "bitcoin:")) |
|
||||||
{ |
|
||||||
const char *strURI = argv[i]; |
|
||||||
try { |
|
||||||
boost::interprocess::message_queue mq(boost::interprocess::open_only, strBitcoinURIQueueName.c_str()); |
|
||||||
if (mq.try_send(strURI, strlen(strURI), 0)) |
|
||||||
fSent = true; |
|
||||||
else if (fRelay) |
|
||||||
break; |
|
||||||
} |
|
||||||
catch (boost::interprocess::interprocess_exception &ex) { |
|
||||||
// don't log the "file not found" exception, because that's normal for
|
|
||||||
// the first start of the first instance
|
|
||||||
if (ex.get_error_code() != boost::interprocess::not_found_error || !fRelay) |
|
||||||
{ |
|
||||||
printf("main() - boost interprocess exception #%d: %s\n", ex.get_error_code(), ex.what()); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return fSent; |
|
||||||
} |
|
||||||
|
|
||||||
void ipcScanRelay(int argc, char *argv[]) |
|
||||||
{ |
|
||||||
if (ipcScanCmd(argc, argv, true)) |
|
||||||
exit(0); |
|
||||||
} |
|
||||||
|
|
||||||
static void ipcThread(void* pArg) |
|
||||||
{ |
|
||||||
// Make this thread recognisable as the GUI-IPC thread
|
|
||||||
RenameThread("bitcoin-gui-ipc"); |
|
||||||
|
|
||||||
try |
|
||||||
{ |
|
||||||
ipcThread2(pArg); |
|
||||||
} |
|
||||||
catch (std::exception& e) { |
|
||||||
PrintExceptionContinue(&e, "ipcThread()"); |
|
||||||
} catch (...) { |
|
||||||
PrintExceptionContinue(NULL, "ipcThread()"); |
|
||||||
} |
|
||||||
printf("ipcThread exited\n"); |
|
||||||
} |
|
||||||
|
|
||||||
static void ipcThread2(void* pArg) |
|
||||||
{ |
|
||||||
printf("ipcThread started\n"); |
|
||||||
|
|
||||||
message_queue* mq = (message_queue*)pArg; |
|
||||||
char buffer[MAX_URI_LENGTH + 1] = ""; |
|
||||||
size_t nSize = 0; |
|
||||||
unsigned int nPriority = 0; |
|
||||||
|
|
||||||
loop |
|
||||||
{ |
|
||||||
ptime d = boost::posix_time::microsec_clock::universal_time() + millisec(100); |
|
||||||
if (mq->timed_receive(&buffer, sizeof(buffer), nSize, nPriority, d)) |
|
||||||
{ |
|
||||||
uiInterface.ThreadSafeHandleURI(std::string(buffer, nSize)); |
|
||||||
Sleep(1000); |
|
||||||
} |
|
||||||
|
|
||||||
if (fShutdown) |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
// Remove message queue
|
|
||||||
message_queue::remove(strBitcoinURIQueueName.c_str()); |
|
||||||
// Cleanup allocated memory
|
|
||||||
delete mq; |
|
||||||
} |
|
||||||
|
|
||||||
void ipcInit(int argc, char *argv[]) |
|
||||||
{ |
|
||||||
message_queue* mq = NULL; |
|
||||||
char buffer[MAX_URI_LENGTH + 1] = ""; |
|
||||||
size_t nSize = 0; |
|
||||||
unsigned int nPriority = 0; |
|
||||||
|
|
||||||
try { |
|
||||||
mq = new message_queue(open_or_create, strBitcoinURIQueueName.c_str(), 2, MAX_URI_LENGTH); |
|
||||||
|
|
||||||
// Make sure we don't lose any bitcoin: URIs
|
|
||||||
for (int i = 0; i < 2; i++) |
|
||||||
{ |
|
||||||
ptime d = boost::posix_time::microsec_clock::universal_time() + millisec(1); |
|
||||||
if (mq->timed_receive(&buffer, sizeof(buffer), nSize, nPriority, d)) |
|
||||||
{ |
|
||||||
uiInterface.ThreadSafeHandleURI(std::string(buffer, nSize)); |
|
||||||
} |
|
||||||
else |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
// Make sure only one bitcoin instance is listening
|
|
||||||
message_queue::remove(strBitcoinURIQueueName.c_str()); |
|
||||||
delete mq; |
|
||||||
|
|
||||||
mq = new message_queue(open_or_create, strBitcoinURIQueueName.c_str(), 2, MAX_URI_LENGTH); |
|
||||||
} |
|
||||||
catch (interprocess_exception &ex) { |
|
||||||
printf("ipcInit() - boost interprocess exception #%d: %s\n", ex.get_error_code(), ex.what()); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if (!NewThread(ipcThread, mq)) |
|
||||||
{ |
|
||||||
delete mq; |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
ipcScanCmd(argc, argv, false); |
|
||||||
} |
|
||||||
|
|
||||||
#endif |
|
@ -1,16 +0,0 @@ |
|||||||
#ifndef QTIPCSERVER_H |
|
||||||
#define QTIPCSERVER_H |
|
||||||
|
|
||||||
#include <string> |
|
||||||
|
|
||||||
// Define Bitcoin-Qt message queue name for mainnet
|
|
||||||
#define BITCOINURI_QUEUE_NAME_MAINNET "BitcoinURI" |
|
||||||
// Define Bitcoin-Qt message queue name for testnet
|
|
||||||
#define BITCOINURI_QUEUE_NAME_TESTNET "BitcoinURI-testnet" |
|
||||||
|
|
||||||
extern std::string strBitcoinURIQueueName; |
|
||||||
|
|
||||||
void ipcScanRelay(int argc, char *argv[]); |
|
||||||
void ipcInit(int argc, char *argv[]); |
|
||||||
|
|
||||||
#endif // QTIPCSERVER_H
|
|
Loading…
Reference in new issue