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).pull/145/head
Gavin Andresen
11 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