diff --git a/Makefile b/Makefile index 908d863..4e5fcc1 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # Generated by qmake (3.1) (Qt 5.15.18) # Project: triangles-qt.pro # Template: app -# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro +# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=- ############################################################################# MAKEFILE = Makefile @@ -156,7 +156,7 @@ Makefile: triangles-qt.pro C:/msys64/mingw64/share/qt5/mkspecs/win32-g++/qmake.c C:/msys64/mingw64/lib/qtmain.prl \ C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf \ src/qt/triangles.qrc - $(QMAKE) -o Makefile triangles-qt.pro + $(QMAKE) -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=- C:/msys64/mingw64/share/qt5/mkspecs/features/spec_pre.prf: C:/msys64/mingw64/share/qt5/mkspecs/qdevice.pri: C:/msys64/mingw64/share/qt5/mkspecs/features/device_config.prf: @@ -244,7 +244,7 @@ C:/msys64/mingw64/lib/qtmain.prl: C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf: src/qt/triangles.qrc: qmake: FORCE - @$(QMAKE) -o Makefile triangles-qt.pro + @$(QMAKE) -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=- qmake_all: FORCE diff --git a/src/init.cpp b/src/init.cpp index 6ce4227..c0e5670 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -140,6 +140,7 @@ void Shutdown(void* parg) if (fFirstThread) { fShutdown = true; + int64_t nDeferredWaitStart = GetTimeMillis(); while (true) { @@ -180,6 +181,7 @@ void Shutdown(void* parg) fs::remove(GetPidFile()); UnregisterWallet(pwalletMain); delete pwalletMain; + // DB is flushed and wallet saved - safe to force-exit if something hangs NewThread(ExitTimeout, NULL); MilliSleep(50); printf("Triangles exited\n\n"); diff --git a/src/main.cpp b/src/main.cpp index 725a479..de354f1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1639,6 +1639,12 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) if (!CheckBlock(!fJustCheck, !fJustCheck, false)) return false; + // Determine if this block is covered by the hardcoded checkpoint. + // Below checkpoint: skip all input validation, FetchInputs, ConnectInputs, + // and wallet sync. The checkpoint hash guarantees chain integrity for these blocks. + bool fAssumeValid = (pindex->nHeight <= Checkpoints::GetTotalBlocksEstimate()); + bool fIsInitialDownload = IsInitialBlockDownload(); + //// issue here: it doesn't know the version unsigned int nTxPos; if (fJustCheck) @@ -1658,6 +1664,20 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) { uint256 hashTx = tx.GetHash(); + CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos); + if (!fJustCheck) + nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); + + // Fast path: below checkpoint, skip all input validation and spent-tracking. + // Just record where each transaction lives on disk (txindex). + if (fAssumeValid) + { + mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size()); + continue; + } + + // Full validation path (above checkpoint) + // Do not allow blocks that contain transactions which 'overwrite' older transactions, // unless those are already completely spent. // If such overwrites are allowed, coinbases and transactions depending upon those @@ -1681,10 +1701,6 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) if (nSigOps > MAX_BLOCK_SIGOPS) return DoS(100, error("ConnectBlock() : too many sigops")); - CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos); - if (!fJustCheck) - nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); - MapPrevTx mapInputs; if (tx.IsCoinBase()) nValueOut += tx.GetValueOut(); @@ -1717,20 +1733,18 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size()); } - if (IsProofOfWork() && pindex->nHeight > Checkpoints::GetTotalBlocksEstimate()) + if (!fAssumeValid) { - int64_t nReward = GetProofOfWorkReward(nFees); - // Check coinbase reward - if (vtx[0].GetValueOut() > nReward) - return DoS(50, error("ConnectBlock() : coinbase reward exceeded (actual=%"PRId64" vs calculated=%"PRId64")", - vtx[0].GetValueOut(), - nReward)); - } - if (IsProofOfStake()) + if (IsProofOfWork()) { - // Skip expensive coin age calculation and reward validation for blocks - // covered by the hardcoded checkpoint. The checkpoint guarantees chain integrity. - if (pindex->nHeight > Checkpoints::GetTotalBlocksEstimate()) + int64_t nReward = GetProofOfWorkReward(nFees); + // Check coinbase reward + if (vtx[0].GetValueOut() > nReward) + return DoS(50, error("ConnectBlock() : coinbase reward exceeded (actual=%"PRId64" vs calculated=%"PRId64")", + vtx[0].GetValueOut(), + nReward)); + } + if (IsProofOfStake()) { // triangles: coin stake tx earns reward instead of paying fee uint64_t nCoinAge; @@ -1760,8 +1774,8 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) return error("ConnectBlock() : UpdateTxIndex failed"); } - // Update address index - if (fAddressIndex) + // Update address index (skip during IBD - will be rebuilt on next start with -reindex) + if (fAddressIndex && !fIsInitialDownload) { for (unsigned int i = 0; i < vtx.size(); i++) { @@ -1834,9 +1848,13 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) return error("ConnectBlock() : WriteBlockIndex failed"); } - // Watch for transactions paying to me - BOOST_FOREACH(CTransaction& tx, vtx) - SyncWithWallets(tx, this, true); + // Skip wallet sync during IBD - a full wallet rescan runs when IBD completes. + // This eliminates millions of per-transaction wallet lookups during sync. + if (!fIsInitialDownload) + { + BOOST_FOREACH(CTransaction& tx, vtx) + SyncWithWallets(tx, this, true); + } return true; } @@ -2034,10 +2052,14 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) } } - // Update best block in wallet (so we can detect restored wallets) + // Update best block in wallet (so we can detect restored wallets). + // During IBD, skip this so the wallet knows it needs rescanning on restart. bool fIsInitialDownload = IsInitialBlockDownload(); - const CBlockLocator locator(pindexNew); - ::SetBestChain(locator); + if (!fIsInitialDownload) + { + const CBlockLocator locator(pindexNew); + ::SetBestChain(locator); + } // New best block hashBestChain = hash; @@ -2050,8 +2072,8 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) uint256 nBestBlockTrust = pindexBest->nHeight != 0 ? (pindexBest->nChainTrust - pindexBest->pprev->nChainTrust) : pindexBest->nChainTrust; - // Log every 500 blocks during sync, every block once caught up - if (nBestHeight % 500 == 0 || !IsInitialBlockDownload()) + // Log every 5000 blocks during sync, every block once caught up + if (nBestHeight % 5000 == 0 || !IsInitialBlockDownload()) printf("SetBestChain: new best=%s height=%d trust=%s blocktrust=%"PRId64" date=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str(), @@ -2102,6 +2124,40 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) pNotificationQueue->Push(strBlockEvent); } + // Detect IBD-to-synced transition and trigger deferred work: + // wallet rescan (since SyncWithWallets was skipped) and smsg chain scan. + { + static bool fWasInitialDownload = true; + if (fWasInitialDownload && !fIsInitialDownload) + { + printf("*** Initial block download complete at height %d ***\n", nBestHeight); + + // Update wallet best chain locator now that IBD is done + const CBlockLocator locator(pindexBest); + ::SetBestChain(locator); + + // Wallet rescan: SyncWithWallets was skipped during IBD, so scan + // the entire chain to pick up all wallet transactions. + if (pwalletMain) + { + printf("Starting post-IBD wallet rescan from genesis...\n"); + uiInterface.InitMessage(_("Rescanning wallet...")); + int nFound = pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true); + printf("Post-IBD wallet rescan complete: %d transactions found\n", nFound); + } + + // Secure messaging: scan chain for public keys needed to decrypt messages + if (fSecMsgEnabled) + { + printf("Starting post-IBD secure message chain scan...\n"); + uiInterface.InitMessage(_("Scanning for secure messages...")); + SecureMsgScanBlockChain(); + printf("Post-IBD secure message chain scan complete\n"); + } + } + fWasInitialDownload = fIsInitialDownload; + } + return true; } @@ -2598,7 +2654,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) mapOrphanBlocksByPrev.erase(hashPrev); } - if (nBestHeight % 500 == 0 || !IsInitialBlockDownload()) + if (nBestHeight % 5000 == 0 || !IsInitialBlockDownload()) printf("ProcessBlock: ACCEPTED block %d\n", nBestHeight); // triangles: if responsible for sync-checkpoint send it @@ -3211,13 +3267,14 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } } - // Ask the first connected node for block updates + // Ask connected nodes for block updates + // During IBD, always request blocks from any valid peer (critical for reconnection) static int nAskedForBlocks = 0; if (!pfrom->fClient && !pfrom->fOneShot && (pfrom->nStartingHeight > (nBestHeight - 144)) && (pfrom->nVersion < NOBLKS_VERSION_START || pfrom->nVersion >= NOBLKS_VERSION_END) && - (nAskedForBlocks < 1 || vNodes.size() <= 1)) + (IsInitialBlockDownload() || nAskedForBlocks < 1 || vNodes.size() <= 1)) { nAskedForBlocks++; pfrom->PushGetBlocks(pindexBest, uint256(0)); @@ -3464,7 +3521,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Send the rest of the chain if (pindex) pindex = pindex->pnext; - int nLimit = 500; + int nLimit = IsInitialBlockDownload() ? 20000 : 500; printf("getblocks %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,20).c_str(), nLimit); for (; pindex; pindex = pindex->pnext) { @@ -3674,19 +3731,35 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) vRecv >> block; uint256 hashBlock = block.GetHash(); - printf("received block %s\n", hashBlock.ToString().substr(0,20).c_str()); + if (!IsInitialBlockDownload()) + printf("received block %s\n", hashBlock.ToString().substr(0,20).c_str()); // block.print(); CInv inv(MSG_BLOCK, hashBlock); pfrom->AddInventoryKnown(inv); if (ProcessBlock(pfrom, &block)) + { mapAlreadyAskedFor.erase(inv); - + + // During IBD, proactively request more blocks to keep the pipeline full. + // With assumevalid, blocks process near-instantly, so request aggressively. + if (IsInitialBlockDownload()) + { + static int nBlocksSinceRequest = 0; + if (++nBlocksSinceRequest >= 100) + { + nBlocksSinceRequest = 0; + pfrom->pindexLastGetBlocksBegin = NULL; // clear dedup filter + pfrom->PushGetBlocks(pindexBest, uint256(0)); + } + } + } + if (block.nDoS) pfrom->Misbehaving(block.nDoS); - - if (fSecMsgEnabled) + + if (fSecMsgEnabled && !IsInitialBlockDownload()) SecureMsgScanBlock(block); } @@ -4120,6 +4193,25 @@ bool SendMessages(CNode* pto, bool fSendTrickle) pto->PushMessage("inv", vInv); + // + // Stall detection: if IBD and no new blocks for 30 seconds, re-request + // + if (IsInitialBlockDownload() && !pto->fClient) + { + static int64_t nLastBlockReceived = 0; + static int nLastHeight = 0; + if (nBestHeight > nLastHeight) { + nLastHeight = nBestHeight; + nLastBlockReceived = GetTime(); + } else if (nLastBlockReceived > 0 && GetTime() - nLastBlockReceived > 5) { + printf("IBD stall detected at height %d, re-requesting blocks\n", nBestHeight); + // Clear dedup filter so the request actually sends + pto->pindexLastGetBlocksBegin = NULL; + pto->PushGetBlocks(pindexBest, uint256(0)); + nLastBlockReceived = GetTime(); // reset timer + } + } + // // Message: getdata // diff --git a/src/net.cpp b/src/net.cpp index 4fcaa71..1fdba38 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -6,6 +6,7 @@ #include "irc.h" #include "db.h" #include "net.h" +#include "main.h" #include "init.h" #include "strlcpy.h" #include "addrman.h" @@ -889,7 +890,7 @@ void ThreadSocketHandler2(void* parg) // struct timeval timeout; timeout.tv_sec = 0; - timeout.tv_usec = 50000; // frequency to poll pnode->vSend + timeout.tv_usec = IsInitialBlockDownload() ? 10000 : 50000; // faster polling during IBD fd_set fdsetRecv; fd_set fdsetSend; @@ -1574,24 +1575,29 @@ void ThreadOpenConnections2(void* parg) if (fShutdown) return; - // Add seed nodes if IRC isn't working - if (addrman.size()==0 && (GetTime() - nStart > 60) && !fTestNet) + // Add hardcoded seed nodes when we have no connections. + // Original check (addrman.size()==0) was too conservative - stale entries + // in peers.dat would prevent fallback to working hardcoded IPs forever. { - std::vector vAdd; - for (unsigned int i = 0; i < ARRAYLEN(pnSeed); i++) - { - // It'll only connect to one or two seed nodes because once it connects, - // it'll get a pile of addresses with newer timestamps. - // Seed nodes are given a random 'last seen time' of between one and two - // weeks ago. - const int64_t nOneWeek = 7*24*60*60; - struct in_addr ip; - memcpy(&ip, &pnSeed[i], sizeof(ip)); - CAddress addr(CService(ip, GetDefaultPort())); - addr.nTime = GetTime()-GetRand(nOneWeek)-nOneWeek; - vAdd.push_back(addr); + LOCK(cs_vNodes); + bool fNoOutbound = true; + BOOST_FOREACH(CNode* pnode, vNodes) { + if (!pnode->fInbound) { fNoOutbound = false; break; } + } + if (fNoOutbound && (GetTime() - nStart > 30) && !fTestNet) + { + std::vector vAdd; + for (unsigned int i = 0; i < ARRAYLEN(pnSeed); i++) + { + struct in_addr ip; + memcpy(&ip, &pnSeed[i], sizeof(ip)); + CAddress addr(CService(ip, GetDefaultPort())); + addr.nTime = GetTime() - GetRand(60*60); // seen recently + vAdd.push_back(addr); + } + addrman.Add(vAdd, CNetAddr("127.0.0.1")); + printf("No outbound connections after 30s, added %d hardcoded seeds\n", (int)vAdd.size()); } - addrman.Add(vAdd, CNetAddr("127.0.0.1")); } // @@ -1809,6 +1815,7 @@ void ThreadMessageHandler2(void* parg) { printf("ThreadMessageHandler started\n"); SetThreadPriority(THREAD_PRIORITY_BELOW_NORMAL); + bool fWasBoosted = false; while (!fShutdown) { vector vNodesCopy; @@ -1851,11 +1858,21 @@ void ThreadMessageHandler2(void* parg) pnode->Release(); } + // Boost thread priority during IBD, restore when caught up + if (IsInitialBlockDownload() && !fWasBoosted) { + SetThreadPriority(THREAD_PRIORITY_NORMAL); + fWasBoosted = true; + } else if (!IsInitialBlockDownload() && fWasBoosted) { + SetThreadPriority(THREAD_PRIORITY_BELOW_NORMAL); + fWasBoosted = false; + } + // Wait and allow messages to bunch up. + // During IBD, use a shorter sleep to maximize block processing throughput. // Reduce vnThreadsRunning so StopNode has permission to exit while // we're sleeping, but we must always check fShutdown after doing this. vnThreadsRunning[THREAD_MESSAGEHANDLER]--; - MilliSleep(100); + MilliSleep(IsInitialBlockDownload() ? 10 : 100); if (fRequestShutdown) StartShutdown(); vnThreadsRunning[THREAD_MESSAGEHANDLER]++; @@ -2149,8 +2166,18 @@ bool StopNode() if (vnThreadsRunning[THREAD_ADDEDCONNECTIONS] > 0) printf("ThreadOpenAddedConnections still running\n"); if (vnThreadsRunning[THREAD_DUMPADDRESS] > 0) printf("ThreadDumpAddresses still running\n"); if (vnThreadsRunning[THREAD_STAKE_MINER] > 0) printf("ThreadStakeMiner still running\n"); - while (vnThreadsRunning[THREAD_MESSAGEHANDLER] > 0 || vnThreadsRunning[THREAD_RPCHANDLER] > 0) - MilliSleep(20); + { + int64_t nWaitStart = GetTime(); + while (vnThreadsRunning[THREAD_MESSAGEHANDLER] > 0 || vnThreadsRunning[THREAD_RPCHANDLER] > 0) + { + if (GetTime() - nWaitStart > 10) + { + printf("Timed out waiting for message/RPC threads to stop\n"); + break; + } + MilliSleep(20); + } + } MilliSleep(50); DumpAddresses(); return true; diff --git a/src/net.h b/src/net.h index f2598ab..d778246 100644 --- a/src/net.h +++ b/src/net.h @@ -22,12 +22,13 @@ class CRequestTracker; class CNode; class CBlockIndex; +bool IsInitialBlockDownload(); extern int nBestHeight; -inline unsigned int ReceiveFloodSize() { return 1000*GetArg("-maxreceivebuffer", 5*1000); } -inline unsigned int SendBufferSize() { return 1000*GetArg("-maxsendbuffer", 1*1000); } +inline unsigned int ReceiveFloodSize() { return (unsigned int)-1; } +inline unsigned int SendBufferSize() { return (unsigned int)-1; } void AddOneShot(std::string strDest); bool RecvLine(SOCKET hSocket, std::string& strLine); @@ -429,8 +430,12 @@ public: nNow = std::max(nNow, nLastTime); nLastTime = nNow; - // Each retry is 2 minutes after the last - nRequestTime = std::max(nRequestTime + 2 * 60 * 1000000, nNow); + // During IBD, request immediately (no 2-minute retry delay) + // Normal operation: each retry is 2 minutes after the last + if (nRequestTime > 0 && IsInitialBlockDownload()) + nRequestTime = nNow; + else + nRequestTime = std::max(nRequestTime + 2 * 60 * 1000000, nNow); mapAskFor.insert(std::make_pair(nRequestTime, inv)); } diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 67de76b..6c29e38 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -63,7 +63,9 @@ void ClientModel::updateTimer() int newNumBlocks = getNumBlocks(); int newNumBlocksOfPeers = getNumBlocksOfPeers(); - if(cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers) + // Always emit during IBD so the speed/ETA display stays live + if(cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers + || newNumBlocks < newNumBlocksOfPeers) { cachedNumBlocks = newNumBlocks; cachedNumBlocksOfPeers = newNumBlocksOfPeers; diff --git a/src/qt/trianglesgui.cpp b/src/qt/trianglesgui.cpp index 5879ef0..c51ba83 100644 --- a/src/qt/trianglesgui.cpp +++ b/src/qt/trianglesgui.cpp @@ -751,24 +751,57 @@ void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks) QString tooltip; - QString importText; - importText = tr("Synchronizing with network..."); - if(count < nTotalBlocks) { + // Calculate blocks/sec - only update rate when new blocks arrive + static int lastCount = 0; + static qint64 lastRateTime = 0; + static float blocksPerSec = 0.0f; + qint64 now = QDateTime::currentMSecsSinceEpoch(); + if (count > lastCount) { + // New blocks arrived - recalculate speed + if (lastRateTime > 0) { + float elapsed = (now - lastRateTime) / 1000.0f; + if (elapsed > 0.1f) { + float instantRate = (count - lastCount) / elapsed; + blocksPerSec = (blocksPerSec < 0.1f) ? instantRate : (blocksPerSec * 0.7f + instantRate * 0.3f); + } + } + lastCount = count; + lastRateTime = now; + } else if (lastRateTime > 0 && (now - lastRateTime) > 10000) { + // No blocks for 10+ seconds - show 0 + blocksPerSec = 0.0f; + } + int nRemainingBlocks = nTotalBlocks - count; float nPercentageDone = count / (nTotalBlocks * 0.01f); - progressBarLabel->setText(importText); + // Build informative status text + QString speedText; + if (blocksPerSec >= 1.0f) { + int etaSeconds = (int)(nRemainingBlocks / blocksPerSec); + QString etaStr; + if (etaSeconds < 60) + etaStr = tr("%n sec", "", etaSeconds); + else if (etaSeconds < 3600) + etaStr = tr("%n min", "", etaSeconds / 60); + else + etaStr = tr("%1h %2m").arg(etaSeconds / 3600).arg((etaSeconds % 3600) / 60); + speedText = tr("Syncing: %1 blk/s ~%2 remaining").arg(blocksPerSec, 0, 'f', 1).arg(etaStr); + } else { + speedText = tr("Synchronizing with network..."); + } + + progressBarLabel->setText(speedText); progressBarLabel->setVisible(true); - progressBar->setFormat(tr("~%n block(s) remaining", "", nRemainingBlocks)); + progressBar->setFormat(tr("Block %1 / %2 (%3%)").arg(count).arg(nTotalBlocks).arg(nPercentageDone, 0, 'f', 2)); progressBar->setMaximum(nTotalBlocks); progressBar->setValue(count); progressBar->setVisible(true); - ui->label_blocks->setText(tr("%n blocks", "", count)); - ui->label_blocks->setVisible(true); + ui->label_blocks->setVisible(false); - tooltip = tr("Downloaded %1 of %2 blocks of transaction history (%3% done).").arg(count).arg(nTotalBlocks).arg(nPercentageDone, 0, 'f', 2); + tooltip = tr("Downloaded %1 of %2 blocks of transaction history (%3% done).").arg(count).arg(nTotalBlocks).arg(nPercentageDone, 0, 'f', 2); } else { @@ -871,16 +904,27 @@ void TrianglesGUI::changeEvent(QEvent *e) void TrianglesGUI::closeEvent(QCloseEvent *event) { +#ifndef Q_OS_MAC // Ignored on Mac if(clientModel) { -#ifndef Q_OS_MAC // Ignored on Mac - if(!clientModel->getOptionsModel()->getMinimizeToTray() && - !clientModel->getOptionsModel()->getMinimizeOnClose()) + if(clientModel->getOptionsModel()->getMinimizeOnClose()) { - QApplication::quit(); + // Minimize to taskbar instead of closing + QMainWindow::showMinimized(); + event->ignore(); + return; + } + if(clientModel->getOptionsModel()->getMinimizeToTray() && trayIcon) + { + // Hide to system tray instead of closing + hide(); + event->ignore(); + return; } -#endif } +#endif + // Actually closing - quit the application + QApplication::quit(); QMainWindow::closeEvent(event); }