Merge pull request #2299 from gavinandresen/localsocketuri
Reimplement click-to-pay. Support OSX.
This commit is contained in:
commit
7ea7c94cf9
@ -3,6 +3,7 @@ TARGET = bitcoin-qt
|
||||
macx:TARGET = "Bitcoin-Qt"
|
||||
VERSION = 0.8.0
|
||||
INCLUDEPATH += src src/json src/qt
|
||||
QT += network
|
||||
DEFINES += QT_GUI BOOST_THREAD_USE_LIB BOOST_SPIRIT_THREADSAFE
|
||||
CONFIG += no_include_pwd
|
||||
CONFIG += thread
|
||||
@ -196,7 +197,7 @@ HEADERS += src/qt/bitcoingui.h \
|
||||
src/qt/askpassphrasedialog.h \
|
||||
src/protocol.h \
|
||||
src/qt/notificator.h \
|
||||
src/qt/qtipcserver.h \
|
||||
src/qt/paymentserver.h \
|
||||
src/allocators.h \
|
||||
src/ui_interface.h \
|
||||
src/qt/rpcconsole.h \
|
||||
@ -265,7 +266,7 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
|
||||
src/qt/askpassphrasedialog.cpp \
|
||||
src/protocol.cpp \
|
||||
src/qt/notificator.cpp \
|
||||
src/qt/qtipcserver.cpp \
|
||||
src/qt/paymentserver.cpp \
|
||||
src/qt/rpcconsole.cpp \
|
||||
src/noui.cpp \
|
||||
src/leveldb.cpp \
|
||||
@ -386,6 +387,7 @@ macx:ICON = src/qt/res/icons/bitcoin.icns
|
||||
macx:QMAKE_CFLAGS_THREAD += -pthread
|
||||
macx:QMAKE_LFLAGS_THREAD += -pthread
|
||||
macx:QMAKE_CXXFLAGS_THREAD += -pthread
|
||||
macx:QMAKE_INFO_PLIST = share/qt/Info.plist
|
||||
|
||||
# Set libraries and includes at end, to use platform-defined defaults if not overridden
|
||||
INCLUDEPATH += $$BOOST_INCLUDE_PATH $$BDB_INCLUDE_PATH $$OPENSSL_INCLUDE_PATH $$QRENCODE_INCLUDE_PATH
|
||||
|
31
share/qt/Info.plist
Normal file
31
share/qt/Info.plist
Normal file
@ -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>
|
@ -9,12 +9,13 @@
|
||||
#include "guiconstants.h"
|
||||
#include "init.h"
|
||||
#include "ui_interface.h"
|
||||
#include "qtipcserver.h"
|
||||
#include "paymentserver.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMessageBox>
|
||||
#include <QTextCodec>
|
||||
#include <QLocale>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include <QSplashScreen>
|
||||
#include <QLibraryInfo>
|
||||
@ -74,15 +75,6 @@ static bool ThreadSafeAskFee(int64 nFeeRequired)
|
||||
return payFee;
|
||||
}
|
||||
|
||||
static void ThreadSafeHandleURI(const std::string& strURI)
|
||||
{
|
||||
if(!guiref)
|
||||
return;
|
||||
|
||||
QMetaObject::invokeMethod(guiref, "handleURI", GUIUtil::blockingGUIThreadConnection(),
|
||||
Q_ARG(QString, QString::fromStdString(strURI)));
|
||||
}
|
||||
|
||||
static void InitMessage(const std::string &message)
|
||||
{
|
||||
if(splashref)
|
||||
@ -121,14 +113,6 @@ int main(int argc, char *argv[])
|
||||
// Command-line options take precedence:
|
||||
ParseParameters(argc, argv);
|
||||
|
||||
if(GetBoolArg("-testnet")) // Separate message queue name for testnet
|
||||
strBitcoinURIQueueName = BITCOINURI_QUEUE_NAME_TESTNET;
|
||||
else
|
||||
strBitcoinURIQueueName = BITCOINURI_QUEUE_NAME_MAINNET;
|
||||
|
||||
// Do this early as we don't want to bother initializing if we are just calling IPC
|
||||
ipcScanRelay(argc, argv);
|
||||
|
||||
// Internal string conversion is all UTF-8
|
||||
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
|
||||
QTextCodec::setCodecForCStrings(QTextCodec::codecForTr());
|
||||
@ -136,6 +120,12 @@ int main(int argc, char *argv[])
|
||||
Q_INIT_RESOURCE(bitcoin);
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// Do this early as we don't want to bother initializing if we are just calling IPC
|
||||
// ... but do it after creating app, so QCoreApplication::arguments is initialized:
|
||||
if (PaymentServer::ipcSendCommandLine())
|
||||
exit(0);
|
||||
PaymentServer* paymentServer = new PaymentServer(&app);
|
||||
|
||||
// Install global event filter that makes sure that long tooltips can be word-wrapped
|
||||
app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
|
||||
|
||||
@ -192,7 +182,6 @@ int main(int argc, char *argv[])
|
||||
// Subscribe to global signals from core
|
||||
uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox);
|
||||
uiInterface.ThreadSafeAskFee.connect(ThreadSafeAskFee);
|
||||
uiInterface.ThreadSafeHandleURI.connect(ThreadSafeHandleURI);
|
||||
uiInterface.InitMessage.connect(InitMessage);
|
||||
uiInterface.QueueShutdown.connect(QueueShutdown);
|
||||
uiInterface.Translate.connect(Translate);
|
||||
@ -253,8 +242,10 @@ int main(int argc, char *argv[])
|
||||
window.show();
|
||||
}
|
||||
|
||||
// Place this here as guiref has to be defined if we don't want to lose URIs
|
||||
ipcInit(argc, argv);
|
||||
// Now that initialization/startup is done, process any command-line
|
||||
// bitcoin: URIs
|
||||
QObject::connect(paymentServer, SIGNAL(receivedURI(QString)), &window, SLOT(handleURI(QString)));
|
||||
QTimer::singleShot(100, paymentServer, SLOT(uiReady()));
|
||||
|
||||
app.exec();
|
||||
|
||||
|
159
src/qt/paymentserver.cpp
Normal file
159
src/qt/paymentserver.cpp
Normal file
@ -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);
|
||||
}
|
66
src/qt/paymentserver.h
Normal file
66
src/qt/paymentserver.h
Normal file
@ -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
Block a user