Merge pull request #593 (Full URL Support in bitcoin-qt)

This commit is contained in:
Wladimir J. van der Laan 2012-01-26 19:00:02 +01:00
commit 70f55355e2
17 changed files with 212 additions and 6 deletions

View file

@ -145,7 +145,8 @@ HEADERS += src/qt/bitcoingui.h \
src/qt/qvaluecombobox.h \ src/qt/qvaluecombobox.h \
src/qt/askpassphrasedialog.h \ src/qt/askpassphrasedialog.h \
src/protocol.h \ src/protocol.h \
src/qt/notificator.h src/qt/notificator.h \
src/qt/qtipcserver.h
SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
src/qt/transactiontablemodel.cpp \ src/qt/transactiontablemodel.cpp \
@ -194,7 +195,8 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
src/qt/qvaluecombobox.cpp \ src/qt/qvaluecombobox.cpp \
src/qt/askpassphrasedialog.cpp \ src/qt/askpassphrasedialog.cpp \
src/protocol.cpp \ src/protocol.cpp \
src/qt/notificator.cpp src/qt/notificator.cpp \
src/qt/qtipcserver.cpp
RESOURCES += \ RESOURCES += \
src/qt/bitcoin.qrc src/qt/bitcoin.qrc

View file

@ -6,6 +6,5 @@ Exec=/usr/bin/bitcoin-qt
Terminal=false Terminal=false
Type=Application Type=Application
Icon=/usr/share/pixmaps/bitcoin80.xpm Icon=/usr/share/pixmaps/bitcoin80.xpm
#For when bitcoin (finally) properly handles bitcoin: URLs MimeType=x-scheme-handler/bitcoin;
#MimeType=x-scheme-handler/bitcoin;
Categories=Office; Categories=Office;

View file

@ -3,3 +3,4 @@ bitcoin-qt usr/lib/bitcoin
share/pixmaps/bitcoin32.xpm usr/share/pixmaps share/pixmaps/bitcoin32.xpm usr/share/pixmaps
share/pixmaps/bitcoin80.xpm usr/share/pixmaps share/pixmaps/bitcoin80.xpm usr/share/pixmaps
debian/bitcoin-qt.desktop usr/share/applications debian/bitcoin-qt.desktop usr/share/applications
debian/bitcoin-qt.protocol usr/share/kde4/services/

View file

@ -0,0 +1,11 @@
[Protocol]
exec=bitcoin-qt '%u'
protocol=bitcoin
input=none
output=none
helper=true
listing=
reading=false
writing=false
makedir=false
deleting=false

View file

@ -4,8 +4,10 @@ bitcoin (0.5.1-natty1) natty; urgency=low
These should never have been there, bitcoin isnt anonymous without These should never have been there, bitcoin isnt anonymous without
a ton of work that virtually no users will ever be willing and a ton of work that virtually no users will ever be willing and
capable of doing capable of doing
* Add GNOME/KDE support for bitcoin-qt's bitcoin: URI support.
Thanks to luke-jr for the KDE .protocol file.
-- Matt Corallo <matt@bluematt.me> Sat, 7 Jan 2012 13:37:00 -0500 -- Matt Corallo <matt@bluematt.me> Fri, 23 Dec 2011 20:25:00 -0500
bitcoin (0.5.1-natty0) natty; urgency=low bitcoin (0.5.1-natty0) natty; urgency=low

View file

@ -94,6 +94,10 @@ Section -post SEC0001
WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" UninstallString $INSTDIR\uninstall.exe WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" UninstallString $INSTDIR\uninstall.exe
WriteRegDWORD HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoModify 1 WriteRegDWORD HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoModify 1
WriteRegDWORD HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoRepair 1 WriteRegDWORD HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoRepair 1
WriteRegStr HKCR "bitcoin" "URL Protocol" ""
WriteRegStr HKCR "bitcoin" "" "URL:Bitcoin"
WriteRegStr HKCR "bitcoin\DefaultIcon" "" $INSTDIR\bitcoin.exe
WriteRegStr HKCR "bitcoin\shell\open\command" "" '"$INSTDIR\bitcoin.exe" "$$1"'
SectionEnd SectionEnd
# Macro for selecting uninstaller sections # Macro for selecting uninstaller sections
@ -131,6 +135,7 @@ Section -un.post UNSEC0001
DeleteRegValue HKCU "${REGKEY}" Path DeleteRegValue HKCU "${REGKEY}" Path
DeleteRegKey /IfEmpty HKCU "${REGKEY}\Components" DeleteRegKey /IfEmpty HKCU "${REGKEY}\Components"
DeleteRegKey /IfEmpty HKCU "${REGKEY}" DeleteRegKey /IfEmpty HKCU "${REGKEY}"
DeleteRegKey HKCR "bitcoin"
RmDir /REBOOTOK $SMPROGRAMS\$StartMenuGroup RmDir /REBOOTOK $SMPROGRAMS\$StartMenuGroup
RmDir /REBOOTOK $INSTDIR RmDir /REBOOTOK $INSTDIR
Push $R0 Push $R0

View file

@ -273,7 +273,7 @@ bool AppInit2(int argc, char* argv[])
#ifndef QT_GUI #ifndef QT_GUI
for (int i = 1; i < argc; i++) for (int i = 1; i < argc; i++)
if (!IsSwitchChar(argv[i][0])) if (!IsSwitchChar(argv[i][0]) && !(strlen(argv[i]) > 7 && strncasecmp(argv[i], "bitcoin:", 8) == 0))
fCommandLine = true; fCommandLine = true;
if (fCommandLine) if (fCommandLine)

View file

@ -8,6 +8,7 @@
#include "headers.h" #include "headers.h"
#include "init.h" #include "init.h"
#include "qtipcserver.h"
#include <QApplication> #include <QApplication>
#include <QMessageBox> #include <QMessageBox>
@ -18,6 +19,8 @@
#include <QSplashScreen> #include <QSplashScreen>
#include <QLibraryInfo> #include <QLibraryInfo>
#include <boost/interprocess/ipc/message_queue.hpp>
// Need a global reference for the notifications to find the GUI // Need a global reference for the notifications to find the GUI
BitcoinGUI *guiref; BitcoinGUI *guiref;
QSplashScreen *splashref; QSplashScreen *splashref;
@ -79,6 +82,22 @@ bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption, wxWindo
return payFee; return payFee;
} }
void ThreadSafeHandleURL(const std::string& strURL)
{
if(!guiref)
return;
// Call slot on GUI thread.
// If called from another thread, use a blocking QueuedConnection.
Qt::ConnectionType connectionType = Qt::DirectConnection;
if(QThread::currentThread() != QCoreApplication::instance()->thread())
{
connectionType = Qt::BlockingQueuedConnection;
}
QMetaObject::invokeMethod(guiref, "handleURL", connectionType,
Q_ARG(QString, QString::fromStdString(strURL)));
}
void CalledSetStatusBar(const std::string& strText, int nField) void CalledSetStatusBar(const std::string& strText, int nField)
{ {
// Only used for built-in mining, which is disabled, simple ignore // Only used for built-in mining, which is disabled, simple ignore
@ -114,6 +133,25 @@ std::string _(const char* psz)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
// Do this early as we don't want to bother initializing if we are just calling IPC
for (int i = 1; i < argc; i++)
{
if (strlen(argv[i]) > 7 && strncasecmp(argv[i], "bitcoin:", 8) == 0)
{
const char *strURL = argv[i];
try {
boost::interprocess::message_queue mq(boost::interprocess::open_only, "BitcoinURL");
if(mq.try_send(strURL, strlen(strURL), 0))
exit(0);
else
break;
}
catch (boost::interprocess::interprocess_exception &ex) {
break;
}
}
}
// Internal string conversion is all UTF-8 // Internal string conversion is all UTF-8
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
QTextCodec::setCodecForCStrings(QTextCodec::codecForTr()); QTextCodec::setCodecForCStrings(QTextCodec::codecForTr());
@ -185,6 +223,23 @@ int main(int argc, char *argv[])
window.show(); window.show();
} }
// Place this here as guiref has to be defined if we dont want to lose URLs
ipcInit();
// Check for URL in argv
for (int i = 1; i < argc; i++)
{
if (strlen(argv[i]) > 7 && strncasecmp(argv[i], "bitcoin:", 8) == 0)
{
const char *strURL = argv[i];
try {
boost::interprocess::message_queue mq(boost::interprocess::open_only, "BitcoinURL");
mq.try_send(strURL, strlen(strURL), 0);
}
catch (boost::interprocess::interprocess_exception &ex) {
}
}
}
app.exec(); app.exec();
guiref = 0; guiref = 0;

View file

@ -670,6 +670,13 @@ void BitcoinGUI::dropEvent(QDropEvent *event)
event->acceptProposedAction(); event->acceptProposedAction();
} }
void BitcoinGUI::handleURL(QString strURL)
{
gotoSendCoinsPage();
QUrl url = QUrl(strURL);
sendCoinsPage->handleURL(&url);
}
void BitcoinGUI::setEncryptionStatus(int status) void BitcoinGUI::setEncryptionStatus(int status)
{ {
switch(status) switch(status)

View file

@ -123,6 +123,7 @@ public slots:
@param[out] payFee true to pay the fee, false to not pay the fee @param[out] payFee true to pay the fee, false to not pay the fee
*/ */
void askFee(qint64 nFeeRequired, bool *payFee); void askFee(qint64 nFeeRequired, bool *payFee);
void handleURL(QString strURL);
private slots: private slots:
/** Switch to overview (home) page */ /** Switch to overview (home) page */

95
src/qt/qtipcserver.cpp Normal file
View file

@ -0,0 +1,95 @@
// Copyright (c) 2011 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file license.txt or http://www.opensource.org/licenses/mit-license.php.
#include <boost/algorithm/string.hpp>
#include <boost/interprocess/ipc/message_queue.hpp>
#include <boost/tokenizer.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "headers.h"
using namespace boost::interprocess;
using namespace boost::posix_time;
using namespace boost;
using namespace std;
void ipcShutdown()
{
message_queue::remove("BitcoinURL");
}
void ipcThread(void* parg)
{
message_queue* mq = (message_queue*)parg;
char strBuf[257];
size_t nSize;
unsigned int nPriority;
loop
{
ptime d = boost::posix_time::microsec_clock::universal_time() + millisec(100);
if(mq->timed_receive(&strBuf, sizeof(strBuf), nSize, nPriority, d))
{
strBuf[nSize] = '\0';
// Convert bitcoin:// URLs to bitcoin: URIs
if (strBuf[8] == '/' && strBuf[9] == '/')
{
for (int i = 8; i < 256; i++)
{
strBuf[i] = strBuf[i+2];
}
}
ThreadSafeHandleURL(strBuf);
Sleep(1000);
}
if (fShutdown)
{
ipcShutdown();
break;
}
}
ipcShutdown();
}
void ipcInit()
{
message_queue* mq;
char strBuf[257];
size_t nSize;
unsigned int nPriority;
try {
mq = new message_queue(open_or_create, "BitcoinURL", 2, 256);
// 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(&strBuf, sizeof(strBuf), nSize, nPriority, d))
{
strBuf[nSize] = '\0';
// Convert bitcoin:// URLs to bitcoin: URIs
if (strBuf[8] == '/' && strBuf[9] == '/')
{
for (int i = 8; i < 256; i++)
{
strBuf[i] = strBuf[i+2];
}
}
ThreadSafeHandleURL(strBuf);
}
else
break;
}
// Make sure only one bitcoin instance is listening
message_queue::remove("BitcoinURL");
mq = new message_queue(open_or_create, "BitcoinURL", 2, 256);
}
catch (interprocess_exception &ex) {
return;
}
if (!CreateThread(ipcThread, mq))
{
delete mq;
}
}

2
src/qt/qtipcserver.h Normal file
View file

@ -0,0 +1,2 @@
void ipcInit();
void ipcShutdown();

View file

@ -11,6 +11,7 @@
#include <QMessageBox> #include <QMessageBox>
#include <QLocale> #include <QLocale>
#include <QTextDocument> #include <QTextDocument>
#include <QScrollBar>
SendCoinsDialog::SendCoinsDialog(QWidget *parent) : SendCoinsDialog::SendCoinsDialog(QWidget *parent) :
QDialog(parent), QDialog(parent),
@ -29,6 +30,8 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) :
connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry())); connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));
connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
fNewRecipientAllowed = true;
} }
void SendCoinsDialog::setModel(WalletModel *model) void SendCoinsDialog::setModel(WalletModel *model)
@ -91,6 +94,8 @@ void SendCoinsDialog::on_sendButton_clicked()
formatted.append(tr("<b>%1</b> to %2 (%3)").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount), Qt::escape(rcp.label), rcp.address)); formatted.append(tr("<b>%1</b> to %2 (%3)").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount), Qt::escape(rcp.label), rcp.address));
} }
fNewRecipientAllowed = false;
QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"), QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"),
tr("Are you sure you want to send %1?").arg(formatted.join(tr(" and "))), tr("Are you sure you want to send %1?").arg(formatted.join(tr(" and "))),
QMessageBox::Yes|QMessageBox::Cancel, QMessageBox::Yes|QMessageBox::Cancel,
@ -98,6 +103,7 @@ void SendCoinsDialog::on_sendButton_clicked()
if(retval != QMessageBox::Yes) if(retval != QMessageBox::Yes)
{ {
fNewRecipientAllowed = true;
return; return;
} }
@ -105,6 +111,7 @@ void SendCoinsDialog::on_sendButton_clicked()
if(!ctx.isValid()) if(!ctx.isValid())
{ {
// Unlock wallet was cancelled // Unlock wallet was cancelled
fNewRecipientAllowed = true;
return; return;
} }
@ -151,6 +158,7 @@ void SendCoinsDialog::on_sendButton_clicked()
accept(); accept();
break; break;
} }
fNewRecipientAllowed = true;
} }
void SendCoinsDialog::clear() void SendCoinsDialog::clear()
@ -188,6 +196,12 @@ SendCoinsEntry *SendCoinsDialog::addEntry()
// Focus the field, so that entry can start immediately // Focus the field, so that entry can start immediately
entry->clear(); entry->clear();
entry->setFocus();
ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
QCoreApplication::instance()->processEvents();
QScrollBar* bar = ui->scrollArea->verticalScrollBar();
if (bar)
bar->setSliderPosition(bar->maximum());
return entry; return entry;
} }
@ -229,6 +243,9 @@ QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
{ {
if (!fNewRecipientAllowed)
return;
SendCoinsEntry *entry = 0; SendCoinsEntry *entry = 0;
// Replace the first entry if it is still unused // Replace the first entry if it is still unused
if(ui->entries->count() == 1) if(ui->entries->count() == 1)

View file

@ -43,6 +43,7 @@ public slots:
private: private:
Ui::SendCoinsDialog *ui; Ui::SendCoinsDialog *ui;
WalletModel *model; WalletModel *model;
bool fNewRecipientAllowed;
private slots: private slots:
void on_sendButton_clicked(); void on_sendButton_clicked();

View file

@ -151,3 +151,8 @@ bool SendCoinsEntry::isClear()
return ui->payTo->text().isEmpty(); return ui->payTo->text().isEmpty();
} }
void SendCoinsEntry::setFocus()
{
ui->payTo->setFocus();
}

View file

@ -31,6 +31,8 @@ public:
*/ */
QWidget *setupTabChain(QWidget *prev); QWidget *setupTabChain(QWidget *prev);
void setFocus();
public slots: public slots:
void setRemoveEnabled(bool enabled); void setRemoveEnabled(bool enabled);
void clear(); void clear();

View file

@ -40,6 +40,7 @@ extern int MyMessageBox(const std::string& message, const std::string& caption="
#define wxMessageBox MyMessageBox #define wxMessageBox MyMessageBox
extern int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style=wxOK, wxWindow* parent=NULL, int x=-1, int y=-1); extern int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style=wxOK, wxWindow* parent=NULL, int x=-1, int y=-1);
extern bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption, wxWindow* parent); extern bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption, wxWindow* parent);
extern void ThreadSafeHandleURL(const std::string& strURL);
extern void CalledSetStatusBar(const std::string& strText, int nField); extern void CalledSetStatusBar(const std::string& strText, int nField);
extern void UIThreadCall(boost::function0<void> fn); extern void UIThreadCall(boost::function0<void> fn);
extern void MainFrameRepaint(); extern void MainFrameRepaint();