Add support for opening bitcoin: URIs directly.
authorMatt Corallo <matt@bluematt.me>
Sat, 24 Dec 2011 04:27:12 +0000 (20:27 -0800)
committerMatt Corallo <matt@bluematt.me>
Thu, 5 Jan 2012 05:29:28 +0000 (00:29 -0500)
15 files changed:
bitcoin-qt.pro
contrib/debian/bitcoin-qt.desktop
contrib/debian/bitcoin-qt.install
contrib/debian/bitcoin-qt.protocol [new file with mode: 0644]
contrib/debian/changelog
share/setup.nsi
src/init.cpp
src/qt/bitcoin.cpp
src/qt/bitcoingui.cpp
src/qt/bitcoingui.h
src/qt/qtipcserver.cpp [new file with mode: 0644]
src/qt/qtipcserver.h [new file with mode: 0644]
src/qt/sendcoinsdialog.cpp
src/qt/sendcoinsdialog.h
src/qtui.h

index 2352230..ab5414c 100644 (file)
@@ -144,7 +144,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 \
@@ -192,7 +193,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
index d65cc35..ea5a6e4 100644 (file)
@@ -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;
index 7ddc8c1..6a566f5 100644 (file)
@@ -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 (file)
index 0000000..014588d
--- /dev/null
@@ -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
index b7992f7..44aa683 100644 (file)
@@ -1,3 +1,10 @@
+bitcoin (0.5.1-natty1) natty; urgency=low
+
+  * 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>  Fri, 23 Dec 2011 20:25:00 -0500
+
 bitcoin (0.5.1-natty0) natty; urgency=low
 
   * New upstream release.
index eb7a9bc..dcd192f 100644 (file)
@@ -94,6 +94,10 @@ Section -post SEC0001
     WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" UninstallString $INSTDIR\uninstall.exe\r
     WriteRegDWORD HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoModify 1\r
     WriteRegDWORD HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoRepair 1\r
+    WriteRegStr HKCR "bitcoin" "URL Protocol" ""\r
+    WriteRegStr HKCR "bitcoin" "" "URL:Bitcoin"\r
+    WriteRegStr HKCR "bitcoin\DefaultIcon" "" $INSTDIR\bitcoin.exe\r
+    WriteRegStr HKCR "bitcoin\shell\open\command" "" '"$INSTDIR\bitcoin.exe" "$$1"'\r
 SectionEnd\r
 \r
 # Macro for selecting uninstaller sections\r
@@ -131,6 +135,7 @@ Section -un.post UNSEC0001
     DeleteRegValue HKCU "${REGKEY}" Path\r
     DeleteRegKey /IfEmpty HKCU "${REGKEY}\Components"\r
     DeleteRegKey /IfEmpty HKCU "${REGKEY}"\r
+    DeleteRegKey HKCR "bitcoin"\r
     RmDir /REBOOTOK $SMPROGRAMS\$StartMenuGroup\r
     RmDir /REBOOTOK $INSTDIR\r
     Push $R0\r
index 8cbf21c..05ba795 100644 (file)
@@ -272,7 +272,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)
index 894bbb9..85ece9e 100644 (file)
@@ -8,6 +8,7 @@
 
 #include "headers.h"
 #include "init.h"
+#include "qtipcserver.h"
 
 #include <QApplication>
 #include <QMessageBox>
@@ -18,6 +19,8 @@
 #include <QSplashScreen>
 #include <QLibraryInfo>
 
+#include <boost/interprocess/ipc/message_queue.hpp>
+
 // 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;
index d4ce8a7..c9381a6 100644 (file)
@@ -671,6 +671,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)
index a0905e4..d54dc9f 100644 (file)
@@ -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 (file)
index 0000000..2ed8b91
--- /dev/null
@@ -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;
+    }
+}
diff --git a/src/qt/qtipcserver.h b/src/qt/qtipcserver.h
new file mode 100644 (file)
index 0000000..1de0334
--- /dev/null
@@ -0,0 +1,2 @@
+void ipcInit();
+void ipcShutdown();
index 6d32891..0d9a604 100644 (file)
@@ -30,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)
@@ -92,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));
     }
 
+    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,
@@ -99,6 +103,7 @@ void SendCoinsDialog::on_sendButton_clicked()
 
     if(retval != QMessageBox::Yes)
     {
+        fNewRecipientAllowed = true;
         return;
     }
 
@@ -106,6 +111,7 @@ void SendCoinsDialog::on_sendButton_clicked()
     if(!ctx.isValid())
     {
         // Unlock wallet was cancelled
+        fNewRecipientAllowed = true;
         return;
     }
 
@@ -152,6 +158,7 @@ void SendCoinsDialog::on_sendButton_clicked()
         accept();
         break;
     }
+    fNewRecipientAllowed = true;
 }
 
 void SendCoinsDialog::clear()
@@ -236,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)
index 8291025..847ee8b 100644 (file)
@@ -43,6 +43,7 @@ public slots:
 private:
     Ui::SendCoinsDialog *ui;
     WalletModel *model;
+    bool fNewRecipientAllowed;
 
 private slots:
     void on_sendButton_clicked();
index 17fc44e..9791ba5 100644 (file)
@@ -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<void> fn);
 extern void MainFrameRepaint();