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).0.8
Gavin Andresen
12 years ago
7 changed files with 272 additions and 204 deletions
@ -0,0 +1,31 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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