1 // Copyright (c) 2011-2016 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
7 #include "bitcoinaddressvalidator.h"
8 #include "bitcoinunits.h"
9 #include "qvalidatedlineedit.h"
10 #include "walletmodel.h"
13 #include "primitives/transaction.h"
15 #include "policy/policy.h"
17 #include "script/script.h"
18 #include "script/standard.h"
25 #define _WIN32_WINNT 0x0501
29 #define _WIN32_IE 0x0501
30 #define WIN32_LEAN_AND_MEAN 1
39 #include <boost/scoped_array.hpp>
41 #include <QAbstractItemView>
42 #include <QApplication>
45 #include <QDesktopServices>
46 #include <QDesktopWidget>
47 #include <QDoubleValidator>
48 #include <QFileDialog>
52 #include <QTextDocument> // for Qt::mightBeRichText
54 #include <QMouseEvent>
56 #if QT_VERSION < 0x050000
62 #if QT_VERSION >= 0x50200
63 #include <QFontDatabase>
66 static fs::detail::utf8_codecvt_facet utf8
;
69 extern double NSAppKitVersionNumber
;
70 #if !defined(NSAppKitVersionNumber10_8)
71 #define NSAppKitVersionNumber10_8 1187
73 #if !defined(NSAppKitVersionNumber10_9)
74 #define NSAppKitVersionNumber10_9 1265
80 QString
dateTimeStr(const QDateTime
&date
)
82 return date
.date().toString(Qt::SystemLocaleShortDate
) + QString(" ") + date
.toString("hh:mm");
85 QString
dateTimeStr(qint64 nTime
)
87 return dateTimeStr(QDateTime::fromTime_t((qint32
)nTime
));
90 QFont
fixedPitchFont()
92 #if QT_VERSION >= 0x50200
93 return QFontDatabase::systemFont(QFontDatabase::FixedFont
);
95 QFont
font("Monospace");
96 #if QT_VERSION >= 0x040800
97 font
.setStyleHint(QFont::Monospace
);
99 font
.setStyleHint(QFont::TypeWriter
);
105 // Just some dummy data to generate an convincing random-looking (but consistent) address
106 static const uint8_t dummydata
[] = {0xeb,0x15,0x23,0x1d,0xfc,0xeb,0x60,0x92,0x58,0x86,0xb6,0x7d,0x06,0x52,0x99,0x92,0x59,0x15,0xae,0xb1,0x72,0xc0,0x66,0x47};
108 // Generate a dummy address with invalid CRC, starting with the network prefix.
109 static std::string
DummyAddress(const CChainParams
¶ms
)
111 std::vector
<unsigned char> sourcedata
= params
.Base58Prefix(CChainParams::PUBKEY_ADDRESS
);
112 sourcedata
.insert(sourcedata
.end(), dummydata
, dummydata
+ sizeof(dummydata
));
113 for(int i
=0; i
<256; ++i
) { // Try every trailing byte
114 std::string s
= EncodeBase58(sourcedata
.data(), sourcedata
.data() + sourcedata
.size());
115 if (!IsValidDestinationString(s
)) {
118 sourcedata
[sourcedata
.size()-1] += 1;
123 void setupAddressWidget(QValidatedLineEdit
*widget
, QWidget
*parent
)
125 parent
->setFocusProxy(widget
);
127 widget
->setFont(fixedPitchFont());
128 #if QT_VERSION >= 0x040700
129 // We don't want translators to use own addresses in translations
130 // and this is the only place, where this address is supplied.
131 widget
->setPlaceholderText(QObject::tr("Enter a Bitcoin address (e.g. %1)").arg(
132 QString::fromStdString(DummyAddress(Params()))));
134 widget
->setValidator(new BitcoinAddressEntryValidator(parent
));
135 widget
->setCheckValidator(new BitcoinAddressCheckValidator(parent
));
138 void setupAmountWidget(QLineEdit
*widget
, QWidget
*parent
)
140 QDoubleValidator
*amountValidator
= new QDoubleValidator(parent
);
141 amountValidator
->setDecimals(8);
142 amountValidator
->setBottom(0.0);
143 widget
->setValidator(amountValidator
);
144 widget
->setAlignment(Qt::AlignRight
|Qt::AlignVCenter
);
147 bool parseBitcoinURI(const QUrl
&uri
, SendCoinsRecipient
*out
)
149 // return if URI is not valid or is no bitcoin: URI
150 if(!uri
.isValid() || uri
.scheme() != QString("bitcoin"))
153 SendCoinsRecipient rv
;
154 rv
.address
= uri
.path();
155 // Trim any following forward slash which may have been added by the OS
156 if (rv
.address
.endsWith("/")) {
157 rv
.address
.truncate(rv
.address
.length() - 1);
161 #if QT_VERSION < 0x050000
162 QList
<QPair
<QString
, QString
> > items
= uri
.queryItems();
164 QUrlQuery
uriQuery(uri
);
165 QList
<QPair
<QString
, QString
> > items
= uriQuery
.queryItems();
167 for (QList
<QPair
<QString
, QString
> >::iterator i
= items
.begin(); i
!= items
.end(); i
++)
169 bool fShouldReturnFalse
= false;
170 if (i
->first
.startsWith("req-"))
172 i
->first
.remove(0, 4);
173 fShouldReturnFalse
= true;
176 if (i
->first
== "label")
178 rv
.label
= i
->second
;
179 fShouldReturnFalse
= false;
181 if (i
->first
== "message")
183 rv
.message
= i
->second
;
184 fShouldReturnFalse
= false;
186 else if (i
->first
== "amount")
188 if(!i
->second
.isEmpty())
190 if(!BitcoinUnits::parse(BitcoinUnits::BTC
, i
->second
, &rv
.amount
))
195 fShouldReturnFalse
= false;
198 if (fShouldReturnFalse
)
208 bool parseBitcoinURI(QString uri
, SendCoinsRecipient
*out
)
210 // Convert bitcoin:// to bitcoin:
212 // Cannot handle this later, because bitcoin:// will cause Qt to see the part after // as host,
213 // which will lower-case it (and thus invalidate the address).
214 if(uri
.startsWith("bitcoin://", Qt::CaseInsensitive
))
216 uri
.replace(0, 10, "bitcoin:");
218 QUrl
uriInstance(uri
);
219 return parseBitcoinURI(uriInstance
, out
);
222 QString
formatBitcoinURI(const SendCoinsRecipient
&info
)
224 QString ret
= QString("bitcoin:%1").arg(info
.address
);
229 ret
+= QString("?amount=%1").arg(BitcoinUnits::format(BitcoinUnits::BTC
, info
.amount
, false, BitcoinUnits::separatorNever
));
233 if (!info
.label
.isEmpty())
235 QString
lbl(QUrl::toPercentEncoding(info
.label
));
236 ret
+= QString("%1label=%2").arg(paramCount
== 0 ? "?" : "&").arg(lbl
);
240 if (!info
.message
.isEmpty())
242 QString
msg(QUrl::toPercentEncoding(info
.message
));
243 ret
+= QString("%1message=%2").arg(paramCount
== 0 ? "?" : "&").arg(msg
);
250 bool isDust(const QString
& address
, const CAmount
& amount
)
252 CTxDestination dest
= DecodeDestination(address
.toStdString());
253 CScript script
= GetScriptForDestination(dest
);
254 CTxOut
txOut(amount
, script
);
255 return IsDust(txOut
, ::dustRelayFee
);
258 QString
HtmlEscape(const QString
& str
, bool fMultiLine
)
260 #if QT_VERSION < 0x050000
261 QString escaped
= Qt::escape(str
);
263 QString escaped
= str
.toHtmlEscaped();
267 escaped
= escaped
.replace("\n", "<br>\n");
272 QString
HtmlEscape(const std::string
& str
, bool fMultiLine
)
274 return HtmlEscape(QString::fromStdString(str
), fMultiLine
);
277 void copyEntryData(QAbstractItemView
*view
, int column
, int role
)
279 if(!view
|| !view
->selectionModel())
281 QModelIndexList selection
= view
->selectionModel()->selectedRows(column
);
283 if(!selection
.isEmpty())
286 setClipboard(selection
.at(0).data(role
).toString());
290 QList
<QModelIndex
> getEntryData(QAbstractItemView
*view
, int column
)
292 if(!view
|| !view
->selectionModel())
293 return QList
<QModelIndex
>();
294 return view
->selectionModel()->selectedRows(column
);
297 QString
getSaveFileName(QWidget
*parent
, const QString
&caption
, const QString
&dir
,
298 const QString
&filter
,
299 QString
*selectedSuffixOut
)
301 QString selectedFilter
;
303 if(dir
.isEmpty()) // Default to user documents location
305 #if QT_VERSION < 0x050000
306 myDir
= QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation
);
308 myDir
= QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation
);
315 /* Directly convert path to native OS path separators */
316 QString result
= QDir::toNativeSeparators(QFileDialog::getSaveFileName(parent
, caption
, myDir
, filter
, &selectedFilter
));
318 /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */
319 QRegExp
filter_re(".* \\(\\*\\.(.*)[ \\)]");
320 QString selectedSuffix
;
321 if(filter_re
.exactMatch(selectedFilter
))
323 selectedSuffix
= filter_re
.cap(1);
326 /* Add suffix if needed */
327 QFileInfo
info(result
);
328 if(!result
.isEmpty())
330 if(info
.suffix().isEmpty() && !selectedSuffix
.isEmpty())
332 /* No suffix specified, add selected suffix */
333 if(!result
.endsWith("."))
335 result
.append(selectedSuffix
);
339 /* Return selected suffix if asked to */
340 if(selectedSuffixOut
)
342 *selectedSuffixOut
= selectedSuffix
;
347 QString
getOpenFileName(QWidget
*parent
, const QString
&caption
, const QString
&dir
,
348 const QString
&filter
,
349 QString
*selectedSuffixOut
)
351 QString selectedFilter
;
353 if(dir
.isEmpty()) // Default to user documents location
355 #if QT_VERSION < 0x050000
356 myDir
= QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation
);
358 myDir
= QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation
);
365 /* Directly convert path to native OS path separators */
366 QString result
= QDir::toNativeSeparators(QFileDialog::getOpenFileName(parent
, caption
, myDir
, filter
, &selectedFilter
));
368 if(selectedSuffixOut
)
370 /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */
371 QRegExp
filter_re(".* \\(\\*\\.(.*)[ \\)]");
372 QString selectedSuffix
;
373 if(filter_re
.exactMatch(selectedFilter
))
375 selectedSuffix
= filter_re
.cap(1);
377 *selectedSuffixOut
= selectedSuffix
;
382 Qt::ConnectionType
blockingGUIThreadConnection()
384 if(QThread::currentThread() != qApp
->thread())
386 return Qt::BlockingQueuedConnection
;
390 return Qt::DirectConnection
;
394 bool checkPoint(const QPoint
&p
, const QWidget
*w
)
396 QWidget
*atW
= QApplication::widgetAt(w
->mapToGlobal(p
));
397 if (!atW
) return false;
398 return atW
->topLevelWidget() == w
;
401 bool isObscured(QWidget
*w
)
403 return !(checkPoint(QPoint(0, 0), w
)
404 && checkPoint(QPoint(w
->width() - 1, 0), w
)
405 && checkPoint(QPoint(0, w
->height() - 1), w
)
406 && checkPoint(QPoint(w
->width() - 1, w
->height() - 1), w
)
407 && checkPoint(QPoint(w
->width() / 2, w
->height() / 2), w
));
410 void openDebugLogfile()
412 fs::path pathDebug
= GetDataDir() / "debug.log";
414 /* Open debug.log with the associated application */
415 if (fs::exists(pathDebug
))
416 QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathDebug
)));
419 bool openBitcoinConf()
421 boost::filesystem::path pathConfig
= GetConfigFile(BITCOIN_CONF_FILENAME
);
423 /* Create the file */
424 boost::filesystem::ofstream
configFile(pathConfig
, std::ios_base::app
);
426 if (!configFile
.good())
431 /* Open bitcoin.conf with the associated application */
432 return QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathConfig
)));
435 void SubstituteFonts(const QString
& language
)
437 #if defined(Q_OS_MAC)
439 // OSX's default font changed in 10.9 and Qt is unable to find it with its
440 // usual fallback methods when building against the 10.7 sdk or lower.
441 // The 10.8 SDK added a function to let it find the correct fallback font.
442 // If this fallback is not properly loaded, some characters may fail to
445 // The same thing happened with 10.10. .Helvetica Neue DeskInterface is now default.
447 // Solution: If building with the 10.7 SDK or lower and the user's platform
448 // is 10.9 or higher at runtime, substitute the correct font. This needs to
449 // happen before the QApplication is created.
450 #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8
451 if (floor(NSAppKitVersionNumber
) > NSAppKitVersionNumber10_8
)
453 if (floor(NSAppKitVersionNumber
) <= NSAppKitVersionNumber10_9
)
454 /* On a 10.9 - 10.9.x system */
455 QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande");
458 /* 10.10 or later system */
459 if (language
== "zh_CN" || language
== "zh_TW" || language
== "zh_HK") // traditional or simplified Chinese
460 QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Heiti SC");
461 else if (language
== "ja") // Japanese
462 QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Songti SC");
464 QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Lucida Grande");
471 ToolTipToRichTextFilter::ToolTipToRichTextFilter(int _size_threshold
, QObject
*parent
) :
473 size_threshold(_size_threshold
)
478 bool ToolTipToRichTextFilter::eventFilter(QObject
*obj
, QEvent
*evt
)
480 if(evt
->type() == QEvent::ToolTipChange
)
482 QWidget
*widget
= static_cast<QWidget
*>(obj
);
483 QString tooltip
= widget
->toolTip();
484 if(tooltip
.size() > size_threshold
&& !tooltip
.startsWith("<qt") && !Qt::mightBeRichText(tooltip
))
486 // Envelop with <qt></qt> to make sure Qt detects this as rich text
487 // Escape the current message as HTML and replace \n by <br>
488 tooltip
= "<qt>" + HtmlEscape(tooltip
, true) + "</qt>";
489 widget
->setToolTip(tooltip
);
493 return QObject::eventFilter(obj
, evt
);
496 void TableViewLastColumnResizingFixer::connectViewHeadersSignals()
498 connect(tableView
->horizontalHeader(), SIGNAL(sectionResized(int,int,int)), this, SLOT(on_sectionResized(int,int,int)));
499 connect(tableView
->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged()));
502 // We need to disconnect these while handling the resize events, otherwise we can enter infinite loops.
503 void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals()
505 disconnect(tableView
->horizontalHeader(), SIGNAL(sectionResized(int,int,int)), this, SLOT(on_sectionResized(int,int,int)));
506 disconnect(tableView
->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged()));
509 // Setup the resize mode, handles compatibility for Qt5 and below as the method signatures changed.
510 // Refactored here for readability.
511 void TableViewLastColumnResizingFixer::setViewHeaderResizeMode(int logicalIndex
, QHeaderView::ResizeMode resizeMode
)
513 #if QT_VERSION < 0x050000
514 tableView
->horizontalHeader()->setResizeMode(logicalIndex
, resizeMode
);
516 tableView
->horizontalHeader()->setSectionResizeMode(logicalIndex
, resizeMode
);
520 void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex
, int width
)
522 tableView
->setColumnWidth(nColumnIndex
, width
);
523 tableView
->horizontalHeader()->resizeSection(nColumnIndex
, width
);
526 int TableViewLastColumnResizingFixer::getColumnsWidth()
528 int nColumnsWidthSum
= 0;
529 for (int i
= 0; i
< columnCount
; i
++)
531 nColumnsWidthSum
+= tableView
->horizontalHeader()->sectionSize(i
);
533 return nColumnsWidthSum
;
536 int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column
)
538 int nResult
= lastColumnMinimumWidth
;
539 int nTableWidth
= tableView
->horizontalHeader()->width();
543 int nOtherColsWidth
= getColumnsWidth() - tableView
->horizontalHeader()->sectionSize(column
);
544 nResult
= std::max(nResult
, nTableWidth
- nOtherColsWidth
);
550 // Make sure we don't make the columns wider than the table's viewport width.
551 void TableViewLastColumnResizingFixer::adjustTableColumnsWidth()
553 disconnectViewHeadersSignals();
554 resizeColumn(lastColumnIndex
, getAvailableWidthForColumn(lastColumnIndex
));
555 connectViewHeadersSignals();
557 int nTableWidth
= tableView
->horizontalHeader()->width();
558 int nColsWidth
= getColumnsWidth();
559 if (nColsWidth
> nTableWidth
)
561 resizeColumn(secondToLastColumnIndex
,getAvailableWidthForColumn(secondToLastColumnIndex
));
565 // Make column use all the space available, useful during window resizing.
566 void TableViewLastColumnResizingFixer::stretchColumnWidth(int column
)
568 disconnectViewHeadersSignals();
569 resizeColumn(column
, getAvailableWidthForColumn(column
));
570 connectViewHeadersSignals();
573 // When a section is resized this is a slot-proxy for ajustAmountColumnWidth().
574 void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex
, int oldSize
, int newSize
)
576 adjustTableColumnsWidth();
577 int remainingWidth
= getAvailableWidthForColumn(logicalIndex
);
578 if (newSize
> remainingWidth
)
580 resizeColumn(logicalIndex
, remainingWidth
);
584 // When the table's geometry is ready, we manually perform the stretch of the "Message" column,
585 // as the "Stretch" resize mode does not allow for interactive resizing.
586 void TableViewLastColumnResizingFixer::on_geometriesChanged()
588 if ((getColumnsWidth() - this->tableView
->horizontalHeader()->width()) != 0)
590 disconnectViewHeadersSignals();
591 resizeColumn(secondToLastColumnIndex
, getAvailableWidthForColumn(secondToLastColumnIndex
));
592 connectViewHeadersSignals();
597 * Initializes all internal variables and prepares the
598 * the resize modes of the last 2 columns of the table and
600 TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer(QTableView
* table
, int lastColMinimumWidth
, int allColsMinimumWidth
, QObject
*parent
) :
603 lastColumnMinimumWidth(lastColMinimumWidth
),
604 allColumnsMinimumWidth(allColsMinimumWidth
)
606 columnCount
= tableView
->horizontalHeader()->count();
607 lastColumnIndex
= columnCount
- 1;
608 secondToLastColumnIndex
= columnCount
- 2;
609 tableView
->horizontalHeader()->setMinimumSectionSize(allColumnsMinimumWidth
);
610 setViewHeaderResizeMode(secondToLastColumnIndex
, QHeaderView::Interactive
);
611 setViewHeaderResizeMode(lastColumnIndex
, QHeaderView::Interactive
);
615 fs::path
static StartupShortcutPath()
617 std::string chain
= ChainNameFromCommandLine();
618 if (chain
== CBaseChainParams::MAIN
)
619 return GetSpecialFolderPath(CSIDL_STARTUP
) / "Bitcoin.lnk";
620 if (chain
== CBaseChainParams::TESTNET
) // Remove this special case when CBaseChainParams::TESTNET = "testnet4"
621 return GetSpecialFolderPath(CSIDL_STARTUP
) / "Bitcoin (testnet).lnk";
622 return GetSpecialFolderPath(CSIDL_STARTUP
) / strprintf("Bitcoin (%s).lnk", chain
);
625 bool GetStartOnSystemStartup()
627 // check for Bitcoin*.lnk
628 return fs::exists(StartupShortcutPath());
631 bool SetStartOnSystemStartup(bool fAutoStart
)
633 // If the shortcut exists already, remove it for updating
634 fs::remove(StartupShortcutPath());
638 CoInitialize(nullptr);
640 // Get a pointer to the IShellLink interface.
641 IShellLink
* psl
= nullptr;
642 HRESULT hres
= CoCreateInstance(CLSID_ShellLink
, nullptr,
643 CLSCTX_INPROC_SERVER
, IID_IShellLink
,
644 reinterpret_cast<void**>(&psl
));
648 // Get the current executable path
649 TCHAR pszExePath
[MAX_PATH
];
650 GetModuleFileName(nullptr, pszExePath
, sizeof(pszExePath
));
652 // Start client minimized
653 QString strArgs
= "-min";
654 // Set -testnet /-regtest options
655 strArgs
+= QString::fromStdString(strprintf(" -testnet=%d -regtest=%d", gArgs
.GetBoolArg("-testnet", false), gArgs
.GetBoolArg("-regtest", false)));
658 boost::scoped_array
<TCHAR
> args(new TCHAR
[strArgs
.length() + 1]);
659 // Convert the QString to TCHAR*
660 strArgs
.toWCharArray(args
.get());
661 // Add missing '\0'-termination to string
662 args
[strArgs
.length()] = '\0';
665 // Set the path to the shortcut target
666 psl
->SetPath(pszExePath
);
667 PathRemoveFileSpec(pszExePath
);
668 psl
->SetWorkingDirectory(pszExePath
);
669 psl
->SetShowCmd(SW_SHOWMINNOACTIVE
);
671 psl
->SetArguments(strArgs
.toStdString().c_str());
673 psl
->SetArguments(args
.get());
676 // Query IShellLink for the IPersistFile interface for
677 // saving the shortcut in persistent storage.
678 IPersistFile
* ppf
= nullptr;
679 hres
= psl
->QueryInterface(IID_IPersistFile
, reinterpret_cast<void**>(&ppf
));
682 WCHAR pwsz
[MAX_PATH
];
683 // Ensure that the string is ANSI.
684 MultiByteToWideChar(CP_ACP
, 0, StartupShortcutPath().string().c_str(), -1, pwsz
, MAX_PATH
);
685 // Save the link by calling IPersistFile::Save.
686 hres
= ppf
->Save(pwsz
, TRUE
);
699 #elif defined(Q_OS_LINUX)
701 // Follow the Desktop Application Autostart Spec:
702 // http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html
704 fs::path
static GetAutostartDir()
706 char* pszConfigHome
= getenv("XDG_CONFIG_HOME");
707 if (pszConfigHome
) return fs::path(pszConfigHome
) / "autostart";
708 char* pszHome
= getenv("HOME");
709 if (pszHome
) return fs::path(pszHome
) / ".config" / "autostart";
713 fs::path
static GetAutostartFilePath()
715 std::string chain
= ChainNameFromCommandLine();
716 if (chain
== CBaseChainParams::MAIN
)
717 return GetAutostartDir() / "bitcoin.desktop";
718 return GetAutostartDir() / strprintf("bitcoin-%s.lnk", chain
);
721 bool GetStartOnSystemStartup()
723 fs::ifstream
optionFile(GetAutostartFilePath());
724 if (!optionFile
.good())
726 // Scan through file for "Hidden=true":
728 while (!optionFile
.eof())
730 getline(optionFile
, line
);
731 if (line
.find("Hidden") != std::string::npos
&&
732 line
.find("true") != std::string::npos
)
740 bool SetStartOnSystemStartup(bool fAutoStart
)
743 fs::remove(GetAutostartFilePath());
746 char pszExePath
[MAX_PATH
+1];
747 memset(pszExePath
, 0, sizeof(pszExePath
));
748 if (readlink("/proc/self/exe", pszExePath
, sizeof(pszExePath
)-1) == -1)
751 fs::create_directories(GetAutostartDir());
753 fs::ofstream
optionFile(GetAutostartFilePath(), std::ios_base::out
|std::ios_base::trunc
);
754 if (!optionFile
.good())
756 std::string chain
= ChainNameFromCommandLine();
757 // Write a bitcoin.desktop file to the autostart directory:
758 optionFile
<< "[Desktop Entry]\n";
759 optionFile
<< "Type=Application\n";
760 if (chain
== CBaseChainParams::MAIN
)
761 optionFile
<< "Name=Bitcoin\n";
763 optionFile
<< strprintf("Name=Bitcoin (%s)\n", chain
);
764 optionFile
<< "Exec=" << pszExePath
<< strprintf(" -min -testnet=%d -regtest=%d\n", gArgs
.GetBoolArg("-testnet", false), gArgs
.GetBoolArg("-regtest", false));
765 optionFile
<< "Terminal=false\n";
766 optionFile
<< "Hidden=false\n";
773 #elif defined(Q_OS_MAC)
774 #pragma GCC diagnostic push
775 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
776 // based on: https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m
778 #include <CoreFoundation/CoreFoundation.h>
779 #include <CoreServices/CoreServices.h>
781 LSSharedFileListItemRef
findStartupItemInList(LSSharedFileListRef list
, CFURLRef findUrl
);
782 LSSharedFileListItemRef
findStartupItemInList(LSSharedFileListRef list
, CFURLRef findUrl
)
784 CFArrayRef listSnapshot
= LSSharedFileListCopySnapshot(list
, nullptr);
785 if (listSnapshot
== nullptr) {
789 // loop through the list of startup items and try to find the bitcoin app
790 for(int i
= 0; i
< CFArrayGetCount(listSnapshot
); i
++) {
791 LSSharedFileListItemRef item
= (LSSharedFileListItemRef
)CFArrayGetValueAtIndex(listSnapshot
, i
);
792 UInt32 resolutionFlags
= kLSSharedFileListNoUserInteraction
| kLSSharedFileListDoNotMountVolumes
;
793 CFURLRef currentItemURL
= nullptr;
795 #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED >= 10100
796 if(&LSSharedFileListItemCopyResolvedURL
)
797 currentItemURL
= LSSharedFileListItemCopyResolvedURL(item
, resolutionFlags
, nullptr);
798 #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 10100
800 LSSharedFileListItemResolve(item
, resolutionFlags
, ¤tItemURL
, nullptr);
803 LSSharedFileListItemResolve(item
, resolutionFlags
, ¤tItemURL
, nullptr);
807 if (CFEqual(currentItemURL
, findUrl
)) {
809 CFRelease(listSnapshot
);
810 CFRelease(currentItemURL
);
813 CFRelease(currentItemURL
);
817 CFRelease(listSnapshot
);
821 bool GetStartOnSystemStartup()
823 CFURLRef bitcoinAppUrl
= CFBundleCopyBundleURL(CFBundleGetMainBundle());
824 if (bitcoinAppUrl
== nullptr) {
828 LSSharedFileListRef loginItems
= LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems
, nullptr);
829 LSSharedFileListItemRef foundItem
= findStartupItemInList(loginItems
, bitcoinAppUrl
);
831 CFRelease(bitcoinAppUrl
);
832 return !!foundItem
; // return boolified object
835 bool SetStartOnSystemStartup(bool fAutoStart
)
837 CFURLRef bitcoinAppUrl
= CFBundleCopyBundleURL(CFBundleGetMainBundle());
838 if (bitcoinAppUrl
== nullptr) {
842 LSSharedFileListRef loginItems
= LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems
, nullptr);
843 LSSharedFileListItemRef foundItem
= findStartupItemInList(loginItems
, bitcoinAppUrl
);
845 if(fAutoStart
&& !foundItem
) {
846 // add bitcoin app to startup item list
847 LSSharedFileListInsertItemURL(loginItems
, kLSSharedFileListItemBeforeFirst
, nullptr, nullptr, bitcoinAppUrl
, nullptr, nullptr);
849 else if(!fAutoStart
&& foundItem
) {
851 LSSharedFileListItemRemove(loginItems
, foundItem
);
854 CFRelease(bitcoinAppUrl
);
857 #pragma GCC diagnostic pop
860 bool GetStartOnSystemStartup() { return false; }
861 bool SetStartOnSystemStartup(bool fAutoStart
) { return false; }
865 void setClipboard(const QString
& str
)
867 QApplication::clipboard()->setText(str
, QClipboard::Clipboard
);
868 QApplication::clipboard()->setText(str
, QClipboard::Selection
);
871 fs::path
qstringToBoostPath(const QString
&path
)
873 return fs::path(path
.toStdString(), utf8
);
876 QString
boostPathToQString(const fs::path
&path
)
878 return QString::fromStdString(path
.string(utf8
));
881 QString
formatDurationStr(int secs
)
884 int days
= secs
/ 86400;
885 int hours
= (secs
% 86400) / 3600;
886 int mins
= (secs
% 3600) / 60;
887 int seconds
= secs
% 60;
890 strList
.append(QString(QObject::tr("%1 d")).arg(days
));
892 strList
.append(QString(QObject::tr("%1 h")).arg(hours
));
894 strList
.append(QString(QObject::tr("%1 m")).arg(mins
));
895 if (seconds
|| (!days
&& !hours
&& !mins
))
896 strList
.append(QString(QObject::tr("%1 s")).arg(seconds
));
898 return strList
.join(" ");
901 QString
formatServicesStr(quint64 mask
)
905 // Just scan the last 8 bits for now.
906 for (int i
= 0; i
< 8; i
++) {
907 uint64_t check
= 1 << i
;
913 strList
.append("NETWORK");
916 strList
.append("GETUTXO");
919 strList
.append("BLOOM");
922 strList
.append("WITNESS");
925 strList
.append("XTHIN");
928 strList
.append(QString("%1[%2]").arg("UNKNOWN").arg(check
));
934 return strList
.join(" & ");
936 return QObject::tr("None");
939 QString
formatPingTime(double dPingTime
)
941 return (dPingTime
== std::numeric_limits
<int64_t>::max()/1e6
|| dPingTime
== 0) ? QObject::tr("N/A") : QString(QObject::tr("%1 ms")).arg(QString::number((int)(dPingTime
* 1000), 10));
944 QString
formatTimeOffset(int64_t nTimeOffset
)
946 return QString(QObject::tr("%1 s")).arg(QString::number((int)nTimeOffset
, 10));
949 QString
formatNiceTimeOffset(qint64 secs
)
951 // Represent time from last generated block in human readable text
952 QString timeBehindText
;
953 const int HOUR_IN_SECONDS
= 60*60;
954 const int DAY_IN_SECONDS
= 24*60*60;
955 const int WEEK_IN_SECONDS
= 7*24*60*60;
956 const int YEAR_IN_SECONDS
= 31556952; // Average length of year in Gregorian calendar
959 timeBehindText
= QObject::tr("%n second(s)","",secs
);
961 else if(secs
< 2*HOUR_IN_SECONDS
)
963 timeBehindText
= QObject::tr("%n minute(s)","",secs
/60);
965 else if(secs
< 2*DAY_IN_SECONDS
)
967 timeBehindText
= QObject::tr("%n hour(s)","",secs
/HOUR_IN_SECONDS
);
969 else if(secs
< 2*WEEK_IN_SECONDS
)
971 timeBehindText
= QObject::tr("%n day(s)","",secs
/DAY_IN_SECONDS
);
973 else if(secs
< YEAR_IN_SECONDS
)
975 timeBehindText
= QObject::tr("%n week(s)","",secs
/WEEK_IN_SECONDS
);
979 qint64 years
= secs
/ YEAR_IN_SECONDS
;
980 qint64 remainder
= secs
% YEAR_IN_SECONDS
;
981 timeBehindText
= QObject::tr("%1 and %2").arg(QObject::tr("%n year(s)", "", years
)).arg(QObject::tr("%n week(s)","", remainder
/WEEK_IN_SECONDS
));
983 return timeBehindText
;
986 void ClickableLabel::mouseReleaseEvent(QMouseEvent
*event
)
988 Q_EMIT
clicked(event
->pos());
991 void ClickableProgressBar::mouseReleaseEvent(QMouseEvent
*event
)
993 Q_EMIT
clicked(event
->pos());
996 } // namespace GUIUtil