diff --git a/bitcoin-qt.pro b/bitcoin-qt.pro index 71ee1fee6..cd68720fe 100644 --- a/bitcoin-qt.pro +++ b/bitcoin-qt.pro @@ -145,7 +145,8 @@ HEADERS += src/qt/bitcoingui.h \ src/qt/qvaluecombobox.h \ src/qt/askpassphrasedialog.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 \ src/qt/transactiontablemodel.cpp \ @@ -194,7 +195,8 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/qt/qvaluecombobox.cpp \ src/qt/askpassphrasedialog.cpp \ src/protocol.cpp \ - src/qt/notificator.cpp + src/qt/notificator.cpp \ + src/qt/qtipcserver.cpp RESOURCES += \ src/qt/bitcoin.qrc diff --git a/contrib/debian/bitcoin-qt.desktop b/contrib/debian/bitcoin-qt.desktop index d65cc35a3..ea5a6e4ac 100644 --- a/contrib/debian/bitcoin-qt.desktop +++ b/contrib/debian/bitcoin-qt.desktop @@ -6,6 +6,5 @@ Exec=/usr/bin/bitcoin-qt Terminal=false Type=Application 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; diff --git a/contrib/debian/bitcoin-qt.install b/contrib/debian/bitcoin-qt.install index 7ddc8c1d8..6a566f515 100644 --- a/contrib/debian/bitcoin-qt.install +++ b/contrib/debian/bitcoin-qt.install @@ -3,3 +3,4 @@ bitcoin-qt usr/lib/bitcoin share/pixmaps/bitcoin32.xpm usr/share/pixmaps share/pixmaps/bitcoin80.xpm usr/share/pixmaps debian/bitcoin-qt.desktop usr/share/applications +debian/bitcoin-qt.protocol usr/share/kde4/services/ diff --git a/contrib/debian/bitcoin-qt.protocol b/contrib/debian/bitcoin-qt.protocol new file mode 100644 index 000000000..014588d53 --- /dev/null +++ b/contrib/debian/bitcoin-qt.protocol @@ -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 diff --git a/contrib/debian/changelog b/contrib/debian/changelog index a26184536..db5e2682c 100644 --- a/contrib/debian/changelog +++ b/contrib/debian/changelog @@ -4,8 +4,10 @@ bitcoin (0.5.1-natty1) natty; urgency=low These should never have been there, bitcoin isnt anonymous without a ton of work that virtually no users will ever be willing and 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 Sat, 7 Jan 2012 13:37:00 -0500 + -- Matt Corallo Fri, 23 Dec 2011 20:25:00 -0500 bitcoin (0.5.1-natty0) natty; urgency=low diff --git a/share/setup.nsi b/share/setup.nsi index eb7a9bc68..dcd192fa5 100644 --- a/share/setup.nsi +++ b/share/setup.nsi @@ -94,6 +94,10 @@ Section -post SEC0001 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)" 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 # Macro for selecting uninstaller sections @@ -131,6 +135,7 @@ Section -un.post UNSEC0001 DeleteRegValue HKCU "${REGKEY}" Path DeleteRegKey /IfEmpty HKCU "${REGKEY}\Components" DeleteRegKey /IfEmpty HKCU "${REGKEY}" + DeleteRegKey HKCR "bitcoin" RmDir /REBOOTOK $SMPROGRAMS\$StartMenuGroup RmDir /REBOOTOK $INSTDIR Push $R0 diff --git a/src/init.cpp b/src/init.cpp index 837d73f95..d237e247e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -273,7 +273,7 @@ bool AppInit2(int argc, char* argv[]) #ifndef QT_GUI 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; if (fCommandLine) diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 894bbb9d1..85ece9e2c 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -8,6 +8,7 @@ #include "headers.h" #include "init.h" +#include "qtipcserver.h" #include #include @@ -18,6 +19,8 @@ #include #include +#include + // Need a global reference for the notifications to find the GUI BitcoinGUI *guiref; QSplashScreen *splashref; @@ -79,6 +82,22 @@ bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption, wxWindo 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) { // 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[]) { + // 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 QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); QTextCodec::setCodecForCStrings(QTextCodec::codecForTr()); @@ -185,6 +223,23 @@ int main(int argc, char *argv[]) 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(); guiref = 0; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index d77279d42..f9f92f0f0 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -670,6 +670,13 @@ void BitcoinGUI::dropEvent(QDropEvent *event) event->acceptProposedAction(); } +void BitcoinGUI::handleURL(QString strURL) +{ + gotoSendCoinsPage(); + QUrl url = QUrl(strURL); + sendCoinsPage->handleURL(&url); +} + void BitcoinGUI::setEncryptionStatus(int status) { switch(status) diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index a0905e44a..d54dc9fcd 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -123,6 +123,7 @@ public slots: @param[out] payFee true to pay the fee, false to not pay the fee */ void askFee(qint64 nFeeRequired, bool *payFee); + void handleURL(QString strURL); private slots: /** Switch to overview (home) page */ diff --git a/src/qt/qtipcserver.cpp b/src/qt/qtipcserver.cpp new file mode 100644 index 000000000..2ed8b915c --- /dev/null +++ b/src/qt/qtipcserver.cpp @@ -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 +#include +#include +#include + +#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; + } +} diff --git a/src/qt/qtipcserver.h b/src/qt/qtipcserver.h new file mode 100644 index 000000000..1de0334af --- /dev/null +++ b/src/qt/qtipcserver.h @@ -0,0 +1,2 @@ +void ipcInit(); +void ipcShutdown(); diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 762f27dfa..0d9a604d3 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -11,6 +11,7 @@ #include #include #include +#include SendCoinsDialog::SendCoinsDialog(QWidget *parent) : QDialog(parent), @@ -29,6 +30,8 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) : connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry())); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); + + fNewRecipientAllowed = true; } void SendCoinsDialog::setModel(WalletModel *model) @@ -91,6 +94,8 @@ void SendCoinsDialog::on_sendButton_clicked() formatted.append(tr("%1 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"), tr("Are you sure you want to send %1?").arg(formatted.join(tr(" and "))), QMessageBox::Yes|QMessageBox::Cancel, @@ -98,6 +103,7 @@ void SendCoinsDialog::on_sendButton_clicked() if(retval != QMessageBox::Yes) { + fNewRecipientAllowed = true; return; } @@ -105,6 +111,7 @@ void SendCoinsDialog::on_sendButton_clicked() if(!ctx.isValid()) { // Unlock wallet was cancelled + fNewRecipientAllowed = true; return; } @@ -151,6 +158,7 @@ void SendCoinsDialog::on_sendButton_clicked() accept(); break; } + fNewRecipientAllowed = true; } void SendCoinsDialog::clear() @@ -188,6 +196,12 @@ SendCoinsEntry *SendCoinsDialog::addEntry() // Focus the field, so that entry can start immediately 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; } @@ -229,6 +243,9 @@ QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) { + if (!fNewRecipientAllowed) + return; + SendCoinsEntry *entry = 0; // Replace the first entry if it is still unused if(ui->entries->count() == 1) diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 82910257f..847ee8b69 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -43,6 +43,7 @@ public slots: private: Ui::SendCoinsDialog *ui; WalletModel *model; + bool fNewRecipientAllowed; private slots: void on_sendButton_clicked(); diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 23b11ccdd..ab5460f8c 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -151,3 +151,8 @@ bool SendCoinsEntry::isClear() return ui->payTo->text().isEmpty(); } +void SendCoinsEntry::setFocus() +{ + ui->payTo->setFocus(); +} + diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index b7f4a0af3..cdbf89326 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -31,6 +31,8 @@ public: */ QWidget *setupTabChain(QWidget *prev); + void setFocus(); + public slots: void setRemoveEnabled(bool enabled); void clear(); diff --git a/src/qtui.h b/src/qtui.h index 17fc44e94..9791ba544 100644 --- a/src/qtui.h +++ b/src/qtui.h @@ -40,6 +40,7 @@ extern int MyMessageBox(const std::string& message, const std::string& caption=" #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 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 UIThreadCall(boost::function0 fn); extern void MainFrameRepaint();