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 (!CBitcoinAddress(s
).IsValid())
117 sourcedata
[sourcedata
.size()-1] += 1;
122 void setupAddressWidget(QValidatedLineEdit
*widget
, QWidget
*parent
)
124 parent
->setFocusProxy(widget
);
126 widget
->setFont(fixedPitchFont());
127 #if QT_VERSION >= 0x040700
128 // We don't want translators to use own addresses in translations
129 // and this is the only place, where this address is supplied.
130 widget
->setPlaceholderText(QObject::tr("Enter a Bitcoin address (e.g. %1)").arg(
131 QString::fromStdString(DummyAddress(Params()))));
133 widget
->setValidator(new BitcoinAddressEntryValidator(parent
));
134 widget
->setCheckValidator(new BitcoinAddressCheckValidator(parent
));
137 void setupAmountWidget(QLineEdit
*widget
, QWidget
*parent
)
139 QDoubleValidator
*amountValidator
= new QDoubleValidator(parent
);
140 amountValidator
->setDecimals(8);
141 amountValidator
->setBottom(0.0);
142 widget
->setValidator(amountValidator
);
143 widget
->setAlignment(Qt::AlignRight
|Qt::AlignVCenter
);
146 bool parseBitcoinURI(const QUrl
&uri
, SendCoinsRecipient
*out
)
148 // return if URI is not valid or is no bitcoin: URI
149 if(!uri
.isValid() || uri
.scheme() != QString("bitcoin"))
152 SendCoinsRecipient rv
;
153 rv
.address
= uri
.path();
154 // Trim any following forward slash which may have been added by the OS
155 if (rv
.address
.endsWith("/")) {
156 rv
.address
.truncate(rv
.address
.length() - 1);
160 #if QT_VERSION < 0x050000
161 QList
<QPair
<QString
, QString
> > items
= uri
.queryItems();
163 QUrlQuery
uriQuery(uri
);
164 QList
<QPair
<QString
, QString
> > items
= uriQuery
.queryItems();
166 for (QList
<QPair
<QString
, QString
> >::iterator i
= items
.begin(); i
!= items
.end(); i
++)
168 bool fShouldReturnFalse
= false;
169 if (i
->first
.startsWith("req-"))
171 i
->first
.remove(0, 4);
172 fShouldReturnFalse
= true;
175 if (i
->first
== "label")
177 rv
.label
= i
->second
;
178 fShouldReturnFalse
= false;
180 if (i
->first
== "message")
182 rv
.message
= i
->second
;
183 fShouldReturnFalse
= false;
185 else if (i
->first
== "amount")
187 if(!i
->second
.isEmpty())
189 if(!BitcoinUnits::parse(BitcoinUnits::BTC
, i
->second
, &rv
.amount
))
194 fShouldReturnFalse
= false;
197 if (fShouldReturnFalse
)
207 bool parseBitcoinURI(QString uri
, SendCoinsRecipient
*out
)
209 // Convert bitcoin:// to bitcoin:
211 // Cannot handle this later, because bitcoin:// will cause Qt to see the part after // as host,
212 // which will lower-case it (and thus invalidate the address).
213 if(uri
.startsWith("bitcoin://", Qt::CaseInsensitive
))
215 uri
.replace(0, 10, "bitcoin:");
217 QUrl
uriInstance(uri
);
218 return parseBitcoinURI(uriInstance
, out
);
221 QString
formatBitcoinURI(const SendCoinsRecipient
&info
)
223 QString ret
= QString("bitcoin:%1").arg(info
.address
);
228 ret
+= QString("?amount=%1").arg(BitcoinUnits::format(BitcoinUnits::BTC
, info
.amount
, false, BitcoinUnits::separatorNever
));
232 if (!info
.label
.isEmpty())
234 QString
lbl(QUrl::toPercentEncoding(info
.label
));
235 ret
+= QString("%1label=%2").arg(paramCount
== 0 ? "?" : "&").arg(lbl
);
239 if (!info
.message
.isEmpty())
241 QString
msg(QUrl::toPercentEncoding(info
.message
));
242 ret
+= QString("%1message=%2").arg(paramCount
== 0 ? "?" : "&").arg(msg
);
249 bool isDust(const QString
& address
, const CAmount
& amount
)
251 CTxDestination dest
= CBitcoinAddress(address
.toStdString()).Get();
252 CScript script
= GetScriptForDestination(dest
);
253 CTxOut
txOut(amount
, script
);
254 return IsDust(txOut
, ::dustRelayFee
);
257 QString
HtmlEscape(const QString
& str
, bool fMultiLine
)
259 #if QT_VERSION < 0x050000
260 QString escaped
= Qt::escape(str
);
262 QString escaped
= str
.toHtmlEscaped();
266 escaped
= escaped
.replace("\n", "<br>\n");
271 QString
HtmlEscape(const std::string
& str
, bool fMultiLine
)
273 return HtmlEscape(QString::fromStdString(str
), fMultiLine
);
276 void copyEntryData(QAbstractItemView
*view
, int column
, int role
)
278 if(!view
|| !view
->selectionModel())
280 QModelIndexList selection
= view
->selectionModel()->selectedRows(column
);
282 if(!selection
.isEmpty())
285 setClipboard(selection
.at(0).data(role
).toString());
289 QList
<QModelIndex
> getEntryData(QAbstractItemView
*view
, int column
)
291 if(!view
|| !view
->selectionModel())
292 return QList
<QModelIndex
>();
293 return view
->selectionModel()->selectedRows(column
);
296 QString
getSaveFileName(QWidget
*parent
, const QString
&caption
, const QString
&dir
,
297 const QString
&filter
,
298 QString
*selectedSuffixOut
)
300 QString selectedFilter
;
302 if(dir
.isEmpty()) // Default to user documents location
304 #if QT_VERSION < 0x050000
305 myDir
= QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation
);
307 myDir
= QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation
);
314 /* Directly convert path to native OS path separators */
315 QString result
= QDir::toNativeSeparators(QFileDialog::getSaveFileName(parent
, caption
, myDir
, filter
, &selectedFilter
));
317 /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */
318 QRegExp
filter_re(".* \\(\\*\\.(.*)[ \\)]");
319 QString selectedSuffix
;
320 if(filter_re
.exactMatch(selectedFilter
))
322 selectedSuffix
= filter_re
.cap(1);
325 /* Add suffix if needed */
326 QFileInfo
info(result
);
327 if(!result
.isEmpty())
329 if(info
.suffix().isEmpty() && !selectedSuffix
.isEmpty())
331 /* No suffix specified, add selected suffix */
332 if(!result
.endsWith("."))
334 result
.append(selectedSuffix
);
338 /* Return selected suffix if asked to */
339 if(selectedSuffixOut
)
341 *selectedSuffixOut
= selectedSuffix
;
346 QString
getOpenFileName(QWidget
*parent
, const QString
&caption
, const QString
&dir
,
347 const QString
&filter
,
348 QString
*selectedSuffixOut
)
350 QString selectedFilter
;
352 if(dir
.isEmpty()) // Default to user documents location
354 #if QT_VERSION < 0x050000
355 myDir
= QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation
);
357 myDir
= QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation
);
364 /* Directly convert path to native OS path separators */
365 QString result
= QDir::toNativeSeparators(QFileDialog::getOpenFileName(parent
, caption
, myDir
, filter
, &selectedFilter
));
367 if(selectedSuffixOut
)
369 /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */
370 QRegExp
filter_re(".* \\(\\*\\.(.*)[ \\)]");
371 QString selectedSuffix
;
372 if(filter_re
.exactMatch(selectedFilter
))
374 selectedSuffix
= filter_re
.cap(1);
376 *selectedSuffixOut
= selectedSuffix
;
381 Qt::ConnectionType
blockingGUIThreadConnection()
383 if(QThread::currentThread() != qApp
->thread())
385 return Qt::BlockingQueuedConnection
;
389 return Qt::DirectConnection
;
393 bool checkPoint(const QPoint
&p
, const QWidget
*w
)
395 QWidget
*atW
= QApplication::widgetAt(w
->mapToGlobal(p
));
396 if (!atW
) return false;
397 return atW
->topLevelWidget() == w
;
400 bool isObscured(QWidget
*w
)
402 return !(checkPoint(QPoint(0, 0), w
)
403 && checkPoint(QPoint(w
->width() - 1, 0), w
)
404 && checkPoint(QPoint(0, w
->height() - 1), w
)
405 && checkPoint(QPoint(w
->width() - 1, w
->height() - 1), w
)
406 && checkPoint(QPoint(w
->width() / 2, w
->height() / 2), w
));
409 void openDebugLogfile()
411 fs::path pathDebug
= GetDataDir() / "debug.log";
413 /* Open debug.log with the associated application */
414 if (fs::exists(pathDebug
))
415 QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathDebug
)));
418 bool openBitcoinConf()
420 boost::filesystem::path pathConfig
= GetConfigFile(BITCOIN_CONF_FILENAME
);
422 /* Create the file */
423 boost::filesystem::ofstream
configFile(pathConfig
, std::ios_base::app
);
425 if (!configFile
.good())
430 /* Open bitcoin.conf with the associated application */
431 return QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathConfig
)));
434 void SubstituteFonts(const QString
& language
)
436 #if defined(Q_OS_MAC)
438 // OSX's default font changed in 10.9 and Qt is unable to find it with its
439 // usual fallback methods when building against the 10.7 sdk or lower.
440 // The 10.8 SDK added a function to let it find the correct fallback font.
441 // If this fallback is not properly loaded, some characters may fail to
444 // The same thing happened with 10.10. .Helvetica Neue DeskInterface is now default.
446 // Solution: If building with the 10.7 SDK or lower and the user's platform
447 // is 10.9 or higher at runtime, substitute the correct font. This needs to
448 // happen before the QApplication is created.
449 #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8
450 if (floor(NSAppKitVersionNumber
) > NSAppKitVersionNumber10_8
)
452 if (floor(NSAppKitVersionNumber
) <= NSAppKitVersionNumber10_9
)
453 /* On a 10.9 - 10.9.x system */
454 QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande");
457 /* 10.10 or later system */
458 if (language
== "zh_CN" || language
== "zh_TW" || language
== "zh_HK") // traditional or simplified Chinese
459 QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Heiti SC");
460 else if (language
== "ja") // Japanese
461 QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Songti SC");
463 QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Lucida Grande");
470 ToolTipToRichTextFilter::ToolTipToRichTextFilter(int _size_threshold
, QObject
*parent
) :
472 size_threshold(_size_threshold
)
477 bool ToolTipToRichTextFilter::eventFilter(QObject
*obj
, QEvent
*evt
)
479 if(evt
->type() == QEvent::ToolTipChange
)
481 QWidget
*widget
= static_cast<QWidget
*>(obj
);
482 QString tooltip
= widget
->toolTip();
483 if(tooltip
.size() > size_threshold
&& !tooltip
.startsWith("<qt") && !Qt::mightBeRichText(tooltip
))
485 // Envelop with <qt></qt> to make sure Qt detects this as rich text
486 // Escape the current message as HTML and replace \n by <br>
487 tooltip
= "<qt>" + HtmlEscape(tooltip
, true) + "</qt>";
488 widget
->setToolTip(tooltip
);
492 return QObject::eventFilter(obj
, evt
);
495 void TableViewLastColumnResizingFixer::connectViewHeadersSignals()
497 connect(tableView
->horizontalHeader(), SIGNAL(sectionResized(int,int,int)), this, SLOT(on_sectionResized(int,int,int)));
498 connect(tableView
->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged()));
501 // We need to disconnect these while handling the resize events, otherwise we can enter infinite loops.
502 void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals()
504 disconnect(tableView
->horizontalHeader(), SIGNAL(sectionResized(int,int,int)), this, SLOT(on_sectionResized(int,int,int)));
505 disconnect(tableView
->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged()));
508 // Setup the resize mode, handles compatibility for Qt5 and below as the method signatures changed.
509 // Refactored here for readability.
510 void TableViewLastColumnResizingFixer::setViewHeaderResizeMode(int logicalIndex
, QHeaderView::ResizeMode resizeMode
)
512 #if QT_VERSION < 0x050000
513 tableView
->horizontalHeader()->setResizeMode(logicalIndex
, resizeMode
);
515 tableView
->horizontalHeader()->setSectionResizeMode(logicalIndex
, resizeMode
);
519 void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex
, int width
)
521 tableView
->setColumnWidth(nColumnIndex
, width
);
522 tableView
->horizontalHeader()->resizeSection(nColumnIndex
, width
);
525 int TableViewLastColumnResizingFixer::getColumnsWidth()
527 int nColumnsWidthSum
= 0;
528 for (int i
= 0; i
< columnCount
; i
++)
530 nColumnsWidthSum
+= tableView
->horizontalHeader()->sectionSize(i
);
532 return nColumnsWidthSum
;
535 int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column
)
537 int nResult
= lastColumnMinimumWidth
;
538 int nTableWidth
= tableView
->horizontalHeader()->width();
542 int nOtherColsWidth
= getColumnsWidth() - tableView
->horizontalHeader()->sectionSize(column
);
543 nResult
= std::max(nResult
, nTableWidth
- nOtherColsWidth
);
549 // Make sure we don't make the columns wider than the table's viewport width.
550 void TableViewLastColumnResizingFixer::adjustTableColumnsWidth()
552 disconnectViewHeadersSignals();
553 resizeColumn(lastColumnIndex
, getAvailableWidthForColumn(lastColumnIndex
));
554 connectViewHeadersSignals();
556 int nTableWidth
= tableView
->horizontalHeader()->width();
557 int nColsWidth
= getColumnsWidth();
558 if (nColsWidth
> nTableWidth
)
560 resizeColumn(secondToLastColumnIndex
,getAvailableWidthForColumn(secondToLastColumnIndex
));
564 // Make column use all the space available, useful during window resizing.
565 void TableViewLastColumnResizingFixer::stretchColumnWidth(int column
)
567 disconnectViewHeadersSignals();
568 resizeColumn(column
, getAvailableWidthForColumn(column
));
569 connectViewHeadersSignals();
572 // When a section is resized this is a slot-proxy for ajustAmountColumnWidth().
573 void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex
, int oldSize
, int newSize
)
575 adjustTableColumnsWidth();
576 int remainingWidth
= getAvailableWidthForColumn(logicalIndex
);
577 if (newSize
> remainingWidth
)
579 resizeColumn(logicalIndex
, remainingWidth
);
583 // When the table's geometry is ready, we manually perform the stretch of the "Message" column,
584 // as the "Stretch" resize mode does not allow for interactive resizing.
585 void TableViewLastColumnResizingFixer::on_geometriesChanged()
587 if ((getColumnsWidth() - this->tableView
->horizontalHeader()->width()) != 0)
589 disconnectViewHeadersSignals();
590 resizeColumn(secondToLastColumnIndex
, getAvailableWidthForColumn(secondToLastColumnIndex
));
591 connectViewHeadersSignals();
596 * Initializes all internal variables and prepares the
597 * the resize modes of the last 2 columns of the table and
599 TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer(QTableView
* table
, int lastColMinimumWidth
, int allColsMinimumWidth
, QObject
*parent
) :
602 lastColumnMinimumWidth(lastColMinimumWidth
),
603 allColumnsMinimumWidth(allColsMinimumWidth
)
605 columnCount
= tableView
->horizontalHeader()->count();
606 lastColumnIndex
= columnCount
- 1;
607 secondToLastColumnIndex
= columnCount
- 2;
608 tableView
->horizontalHeader()->setMinimumSectionSize(allColumnsMinimumWidth
);
609 setViewHeaderResizeMode(secondToLastColumnIndex
, QHeaderView::Interactive
);
610 setViewHeaderResizeMode(lastColumnIndex
, QHeaderView::Interactive
);
614 fs::path
static StartupShortcutPath()
616 std::string chain
= ChainNameFromCommandLine();
617 if (chain
== CBaseChainParams::MAIN
)
618 return GetSpecialFolderPath(CSIDL_STARTUP
) / "Bitcoin.lnk";
619 if (chain
== CBaseChainParams::TESTNET
) // Remove this special case when CBaseChainParams::TESTNET = "testnet4"
620 return GetSpecialFolderPath(CSIDL_STARTUP
) / "Bitcoin (testnet).lnk";
621 return GetSpecialFolderPath(CSIDL_STARTUP
) / strprintf("Bitcoin (%s).lnk", chain
);
624 bool GetStartOnSystemStartup()
626 // check for Bitcoin*.lnk
627 return fs::exists(StartupShortcutPath());
630 bool SetStartOnSystemStartup(bool fAutoStart
)
632 // If the shortcut exists already, remove it for updating
633 fs::remove(StartupShortcutPath());
637 CoInitialize(nullptr);
639 // Get a pointer to the IShellLink interface.
640 IShellLink
* psl
= nullptr;
641 HRESULT hres
= CoCreateInstance(CLSID_ShellLink
, nullptr,
642 CLSCTX_INPROC_SERVER
, IID_IShellLink
,
643 reinterpret_cast<void**>(&psl
));
647 // Get the current executable path
648 TCHAR pszExePath
[MAX_PATH
];
649 GetModuleFileName(nullptr, pszExePath
, sizeof(pszExePath
));
651 // Start client minimized
652 QString strArgs
= "-min";
653 // Set -testnet /-regtest options
654 strArgs
+= QString::fromStdString(strprintf(" -testnet=%d -regtest=%d", gArgs
.GetBoolArg("-testnet", false), gArgs
.GetBoolArg("-regtest", false)));
657 boost::scoped_array
<TCHAR
> args(new TCHAR
[strArgs
.length() + 1]);
658 // Convert the QString to TCHAR*
659 strArgs
.toWCharArray(args
.get());
660 // Add missing '\0'-termination to string
661 args
[strArgs
.length()] = '\0';
664 // Set the path to the shortcut target
665 psl
->SetPath(pszExePath
);
666 PathRemoveFileSpec(pszExePath
);
667 psl
->SetWorkingDirectory(pszExePath
);
668 psl
->SetShowCmd(SW_SHOWMINNOACTIVE
);
670 psl
->SetArguments(strArgs
.toStdString().c_str());
672 psl
->SetArguments(args
.get());
675 // Query IShellLink for the IPersistFile interface for
676 // saving the shortcut in persistent storage.
677 IPersistFile
* ppf
= nullptr;
678 hres
= psl
->QueryInterface(IID_IPersistFile
, reinterpret_cast<void**>(&ppf
));
681 WCHAR pwsz
[MAX_PATH
];
682 // Ensure that the string is ANSI.
683 MultiByteToWideChar(CP_ACP
, 0, StartupShortcutPath().string().c_str(), -1, pwsz
, MAX_PATH
);
684 // Save the link by calling IPersistFile::Save.
685 hres
= ppf
->Save(pwsz
, TRUE
);
698 #elif defined(Q_OS_LINUX)
700 // Follow the Desktop Application Autostart Spec:
701 // http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html
703 fs::path
static GetAutostartDir()
705 char* pszConfigHome
= getenv("XDG_CONFIG_HOME");
706 if (pszConfigHome
) return fs::path(pszConfigHome
) / "autostart";
707 char* pszHome
= getenv("HOME");
708 if (pszHome
) return fs::path(pszHome
) / ".config" / "autostart";
712 fs::path
static GetAutostartFilePath()
714 std::string chain
= ChainNameFromCommandLine();
715 if (chain
== CBaseChainParams::MAIN
)
716 return GetAutostartDir() / "bitcoin.desktop";
717 return GetAutostartDir() / strprintf("bitcoin-%s.lnk", chain
);
720 bool GetStartOnSystemStartup()
722 fs::ifstream
optionFile(GetAutostartFilePath());
723 if (!optionFile
.good())
725 // Scan through file for "Hidden=true":
727 while (!optionFile
.eof())
729 getline(optionFile
, line
);
730 if (line
.find("Hidden") != std::string::npos
&&
731 line
.find("true") != std::string::npos
)
739 bool SetStartOnSystemStartup(bool fAutoStart
)
742 fs::remove(GetAutostartFilePath());
745 char pszExePath
[MAX_PATH
+1];
746 ssize_t r
= readlink("/proc/self/exe", pszExePath
, sizeof(pszExePath
) - 1);
749 pszExePath
[r
] = '\0';
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 // loop through the list of startup items and try to find the bitcoin app
785 CFArrayRef listSnapshot
= LSSharedFileListCopySnapshot(list
, nullptr);
786 for(int i
= 0; i
< CFArrayGetCount(listSnapshot
); i
++) {
787 LSSharedFileListItemRef item
= (LSSharedFileListItemRef
)CFArrayGetValueAtIndex(listSnapshot
, i
);
788 UInt32 resolutionFlags
= kLSSharedFileListNoUserInteraction
| kLSSharedFileListDoNotMountVolumes
;
789 CFURLRef currentItemURL
= nullptr;
791 #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED >= 10100
792 if(&LSSharedFileListItemCopyResolvedURL
)
793 currentItemURL
= LSSharedFileListItemCopyResolvedURL(item
, resolutionFlags
, nullptr);
794 #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 10100
796 LSSharedFileListItemResolve(item
, resolutionFlags
, ¤tItemURL
, nullptr);
799 LSSharedFileListItemResolve(item
, resolutionFlags
, ¤tItemURL
, nullptr);
802 if(currentItemURL
&& CFEqual(currentItemURL
, findUrl
)) {
804 CFRelease(currentItemURL
);
808 CFRelease(currentItemURL
);
814 bool GetStartOnSystemStartup()
816 CFURLRef bitcoinAppUrl
= CFBundleCopyBundleURL(CFBundleGetMainBundle());
817 LSSharedFileListRef loginItems
= LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems
, nullptr);
818 LSSharedFileListItemRef foundItem
= findStartupItemInList(loginItems
, bitcoinAppUrl
);
819 return !!foundItem
; // return boolified object
822 bool SetStartOnSystemStartup(bool fAutoStart
)
824 CFURLRef bitcoinAppUrl
= CFBundleCopyBundleURL(CFBundleGetMainBundle());
825 LSSharedFileListRef loginItems
= LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems
, nullptr);
826 LSSharedFileListItemRef foundItem
= findStartupItemInList(loginItems
, bitcoinAppUrl
);
828 if(fAutoStart
&& !foundItem
) {
829 // add bitcoin app to startup item list
830 LSSharedFileListInsertItemURL(loginItems
, kLSSharedFileListItemBeforeFirst
, nullptr, nullptr, bitcoinAppUrl
, nullptr, nullptr);
832 else if(!fAutoStart
&& foundItem
) {
834 LSSharedFileListItemRemove(loginItems
, foundItem
);
838 #pragma GCC diagnostic pop
841 bool GetStartOnSystemStartup() { return false; }
842 bool SetStartOnSystemStartup(bool fAutoStart
) { return false; }
846 void saveWindowGeometry(const QString
& strSetting
, QWidget
*parent
)
849 settings
.setValue(strSetting
+ "Pos", parent
->pos());
850 settings
.setValue(strSetting
+ "Size", parent
->size());
853 void restoreWindowGeometry(const QString
& strSetting
, const QSize
& defaultSize
, QWidget
*parent
)
856 QPoint pos
= settings
.value(strSetting
+ "Pos").toPoint();
857 QSize size
= settings
.value(strSetting
+ "Size", defaultSize
).toSize();
859 parent
->resize(size
);
862 if ((!pos
.x() && !pos
.y()) || (QApplication::desktop()->screenNumber(parent
) == -1))
864 QRect screen
= QApplication::desktop()->screenGeometry();
865 QPoint
defaultPos((screen
.width() - defaultSize
.width()) / 2,
866 (screen
.height() - defaultSize
.height()) / 2);
867 parent
->resize(defaultSize
);
868 parent
->move(defaultPos
);
872 void setClipboard(const QString
& str
)
874 QApplication::clipboard()->setText(str
, QClipboard::Clipboard
);
875 QApplication::clipboard()->setText(str
, QClipboard::Selection
);
878 fs::path
qstringToBoostPath(const QString
&path
)
880 return fs::path(path
.toStdString(), utf8
);
883 QString
boostPathToQString(const fs::path
&path
)
885 return QString::fromStdString(path
.string(utf8
));
888 QString
formatDurationStr(int secs
)
891 int days
= secs
/ 86400;
892 int hours
= (secs
% 86400) / 3600;
893 int mins
= (secs
% 3600) / 60;
894 int seconds
= secs
% 60;
897 strList
.append(QString(QObject::tr("%1 d")).arg(days
));
899 strList
.append(QString(QObject::tr("%1 h")).arg(hours
));
901 strList
.append(QString(QObject::tr("%1 m")).arg(mins
));
902 if (seconds
|| (!days
&& !hours
&& !mins
))
903 strList
.append(QString(QObject::tr("%1 s")).arg(seconds
));
905 return strList
.join(" ");
908 QString
formatServicesStr(quint64 mask
)
912 // Just scan the last 8 bits for now.
913 for (int i
= 0; i
< 8; i
++) {
914 uint64_t check
= 1 << i
;
920 strList
.append("NETWORK");
923 strList
.append("GETUTXO");
926 strList
.append("BLOOM");
929 strList
.append("WITNESS");
932 strList
.append("XTHIN");
935 strList
.append(QString("%1[%2]").arg("UNKNOWN").arg(check
));
941 return strList
.join(" & ");
943 return QObject::tr("None");
946 QString
formatPingTime(double dPingTime
)
948 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));
951 QString
formatTimeOffset(int64_t nTimeOffset
)
953 return QString(QObject::tr("%1 s")).arg(QString::number((int)nTimeOffset
, 10));
956 QString
formatNiceTimeOffset(qint64 secs
)
958 // Represent time from last generated block in human readable text
959 QString timeBehindText
;
960 const int HOUR_IN_SECONDS
= 60*60;
961 const int DAY_IN_SECONDS
= 24*60*60;
962 const int WEEK_IN_SECONDS
= 7*24*60*60;
963 const int YEAR_IN_SECONDS
= 31556952; // Average length of year in Gregorian calendar
966 timeBehindText
= QObject::tr("%n second(s)","",secs
);
968 else if(secs
< 2*HOUR_IN_SECONDS
)
970 timeBehindText
= QObject::tr("%n minute(s)","",secs
/60);
972 else if(secs
< 2*DAY_IN_SECONDS
)
974 timeBehindText
= QObject::tr("%n hour(s)","",secs
/HOUR_IN_SECONDS
);
976 else if(secs
< 2*WEEK_IN_SECONDS
)
978 timeBehindText
= QObject::tr("%n day(s)","",secs
/DAY_IN_SECONDS
);
980 else if(secs
< YEAR_IN_SECONDS
)
982 timeBehindText
= QObject::tr("%n week(s)","",secs
/WEEK_IN_SECONDS
);
986 qint64 years
= secs
/ YEAR_IN_SECONDS
;
987 qint64 remainder
= secs
% YEAR_IN_SECONDS
;
988 timeBehindText
= QObject::tr("%1 and %2").arg(QObject::tr("%n year(s)", "", years
)).arg(QObject::tr("%n week(s)","", remainder
/WEEK_IN_SECONDS
));
990 return timeBehindText
;
993 void ClickableLabel::mouseReleaseEvent(QMouseEvent
*event
)
995 Q_EMIT
clicked(event
->pos());
998 void ClickableProgressBar::mouseReleaseEvent(QMouseEvent
*event
)
1000 Q_EMIT
clicked(event
->pos());
1003 } // namespace GUIUtil