2021-05-18 15:59:18 +00:00
// SPDX-License-Identifier: BSD-3-Clause
2023-01-02 19:30:11 +00:00
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
2021-05-18 15:59:18 +00:00
# include "WindowManager.h"
2021-06-28 17:48:23 +00:00
2023-03-29 08:14:20 +00:00
# include <QApplication>
2023-01-26 12:12:33 +00:00
# include <QDialogButtonBox>
2021-07-08 00:34:27 +00:00
# include <QInputDialog>
2021-06-28 17:48:23 +00:00
# include <QMessageBox>
2021-05-18 15:59:18 +00:00
# include "constants.h"
2021-06-27 11:46:32 +00:00
# include "dialog/PasswordDialog.h"
# include "dialog/SplashDialog.h"
2021-05-18 15:59:18 +00:00
# include "utils/Icons.h"
# include "utils/NetworkManager.h"
2021-06-28 17:48:23 +00:00
# include "utils/os/tails.h"
2023-03-01 02:05:56 +00:00
# include "utils/os/whonix.h"
2021-05-18 15:59:18 +00:00
# include "utils/TorManager.h"
2021-06-28 17:48:23 +00:00
# include "utils/WebsocketNotifier.h"
2021-05-18 15:59:18 +00:00
2023-03-01 02:05:56 +00:00
WindowManager : : WindowManager ( QObject * parent , EventFilter * eventFilter )
: QObject ( parent )
, eventFilter ( eventFilter )
2022-03-04 16:20:17 +00:00
{
2021-05-18 15:59:18 +00:00
m_walletManager = WalletManager : : instance ( ) ;
2023-03-01 12:28:01 +00:00
m_splashDialog = new SplashDialog ( ) ;
m_cleanupThread = new QThread ( this ) ;
2021-05-18 15:59:18 +00:00
connect ( m_walletManager , & WalletManager : : walletOpened , this , & WindowManager : : onWalletOpened ) ;
connect ( m_walletManager , & WalletManager : : walletCreated , this , & WindowManager : : onWalletCreated ) ;
connect ( m_walletManager , & WalletManager : : deviceButtonRequest , this , & WindowManager : : onDeviceButtonRequest ) ;
2021-07-07 02:38:15 +00:00
connect ( m_walletManager , & WalletManager : : deviceButtonPressed , this , & WindowManager : : onDeviceButtonPressed ) ;
2021-05-18 15:59:18 +00:00
connect ( m_walletManager , & WalletManager : : deviceError , this , & WindowManager : : onDeviceError ) ;
2021-07-08 00:34:27 +00:00
connect ( m_walletManager , & WalletManager : : walletPassphraseNeeded , this , & WindowManager : : onWalletPassphraseNeeded ) ;
2021-05-18 15:59:18 +00:00
connect ( qApp , & QGuiApplication : : lastWindowClosed , this , & WindowManager : : quitAfterLastWindow ) ;
m_tray = new QSystemTrayIcon ( icons ( ) - > icon ( " appicons/64x64.png " ) ) ;
m_tray - > setToolTip ( " Feather Wallet " ) ;
this - > buildTrayMenu ( ) ;
m_tray - > show ( ) ;
this - > initSkins ( ) ;
2023-02-11 17:11:21 +00:00
this - > patchMacStylesheet ( ) ;
2021-05-18 15:59:18 +00:00
2023-01-26 12:12:33 +00:00
this - > showCrashLogs ( ) ;
2021-05-27 01:58:07 +00:00
if ( ! config ( ) - > get ( Config : : firstRun ) . toBool ( ) | | TailsOS : : detect ( ) | | WhonixOS : : detect ( ) ) {
2021-05-18 15:59:18 +00:00
this - > onInitialNetworkConfigured ( ) ;
}
2021-05-26 23:20:37 +00:00
this - > startupWarning ( ) ;
2021-05-18 15:59:18 +00:00
if ( ! this - > autoOpenWallet ( ) ) {
this - > initWizard ( ) ;
}
}
2021-07-11 15:55:42 +00:00
WindowManager : : ~ WindowManager ( ) {
qDebug ( ) < < " ~WindowManager " ;
m_cleanupThread - > quit ( ) ;
m_cleanupThread - > wait ( ) ;
}
2021-05-18 15:59:18 +00:00
// ######################## APPLICATION LIFECYCLE ########################
void WindowManager : : quitAfterLastWindow ( ) {
if ( m_windows . length ( ) > 0 | | m_openingWallet ) {
return ;
}
qDebug ( ) < < " No wizards in progress and no wallets open, quitting application. " ;
2021-05-24 19:56:23 +00:00
this - > close ( ) ;
2021-05-18 15:59:18 +00:00
}
void WindowManager : : close ( ) {
qDebug ( ) < < Q_FUNC_INFO ;
for ( const auto & window : m_windows ) {
window - > close ( ) ;
}
2021-05-25 13:15:19 +00:00
2023-03-01 11:39:26 +00:00
m_wizard - > deleteLater ( ) ;
2023-03-01 12:28:01 +00:00
m_splashDialog - > deleteLater ( ) ;
m_tray - > deleteLater ( ) ;
2023-03-01 11:39:26 +00:00
2021-05-18 15:59:18 +00:00
torManager ( ) - > stop ( ) ;
2021-05-25 13:15:19 +00:00
2021-05-18 15:59:18 +00:00
QApplication : : quit ( ) ;
}
void WindowManager : : closeWindow ( MainWindow * window ) {
2023-03-01 02:05:56 +00:00
qDebug ( ) < < " closing Window " ;
2021-05-18 15:59:18 +00:00
m_windows . removeOne ( window ) ;
2021-07-03 01:29:13 +00:00
2023-03-01 02:05:56 +00:00
// Move Wallet to a different thread for cleanup, so it doesn't block GUI thread
window - > m_wallet - > moveToThread ( m_cleanupThread ) ;
2021-07-03 01:29:13 +00:00
m_cleanupThread - > start ( ) ;
2023-03-01 02:05:56 +00:00
window - > m_wallet - > deleteLater ( ) ;
window - > deleteLater ( ) ;
2021-05-18 15:59:18 +00:00
}
void WindowManager : : restartApplication ( const QString & binaryFilename ) {
QProcess : : startDetached ( binaryFilename , qApp - > arguments ( ) ) ;
this - > close ( ) ;
}
2021-05-26 23:20:37 +00:00
void WindowManager : : startupWarning ( ) {
// Stagenet / Testnet
auto worthlessWarning = QString ( " Feather wallet is currently running in %1 mode. This is meant "
" for developers only. Your coins are WORTHLESS. " ) ;
if ( constants : : networkType = = NetworkType : : STAGENET & & config ( ) - > get ( Config : : warnOnStagenet ) . toBool ( ) ) {
this - > showWarningMessageBox ( " Warning " , worthlessWarning . arg ( " stagenet " ) ) ;
config ( ) - > set ( Config : : warnOnStagenet , false ) ;
}
else if ( constants : : networkType = = NetworkType : : TESTNET & & config ( ) - > get ( Config : : warnOnTestnet ) . toBool ( ) ) {
this - > showWarningMessageBox ( " Warning " , worthlessWarning . arg ( " testnet " ) ) ;
config ( ) - > set ( Config : : warnOnTestnet , false ) ;
}
}
void WindowManager : : showWarningMessageBox ( const QString & title , const QString & message ) {
QMessageBox msgBox ;
msgBox . setWindowIcon ( icons ( ) - > icon ( " appicons/64x64.png " ) ) ;
msgBox . setIcon ( QMessageBox : : Warning ) ;
msgBox . setText ( message ) ;
msgBox . setWindowTitle ( title ) ;
msgBox . setStandardButtons ( QMessageBox : : Ok ) ;
msgBox . setDefaultButton ( QMessageBox : : Ok ) ;
msgBox . exec ( ) ;
}
2022-03-03 22:27:54 +00:00
void WindowManager : : raise ( ) {
if ( ! m_windows . isEmpty ( ) ) {
m_windows . first ( ) - > bringToFront ( ) ;
}
else if ( m_wizard ) {
m_wizard - > show ( ) ;
m_wizard - > raise ( ) ;
m_wizard - > activateWindow ( ) ;
}
else {
// This shouldn't happen
this - > close ( ) ;
}
}
2023-02-11 17:11:21 +00:00
// ######################## SETTINGS ########################
2023-03-01 02:05:56 +00:00
void WindowManager : : showSettings ( Nodes * nodes , QWidget * parent , bool showProxyTab ) {
Settings settings { nodes , parent } ;
2023-02-11 17:11:21 +00:00
2023-02-11 17:24:18 +00:00
connect ( & settings , & Settings : : preferredFiatCurrencyChanged , [ this ] {
2023-02-11 17:11:21 +00:00
for ( const auto & window : m_windows ) {
window - > onPreferredFiatCurrencyChanged ( ) ;
}
} ) ;
2023-02-11 17:24:18 +00:00
connect ( & settings , & Settings : : skinChanged , this , & WindowManager : : onChangeTheme ) ;
connect ( & settings , & Settings : : updateBalance , this , & WindowManager : : updateBalance ) ;
connect ( & settings , & Settings : : proxySettingsChanged , this , & WindowManager : : onProxySettingsChanged ) ;
connect ( & settings , & Settings : : websocketStatusChanged , this , & WindowManager : : onWebsocketStatusChanged ) ;
connect ( & settings , & Settings : : offlineMode , this , & WindowManager : : offlineMode ) ;
connect ( & settings , & Settings : : hideUpdateNotifications , [ this ] ( bool hidden ) {
2023-02-11 17:11:21 +00:00
for ( const auto & window : m_windows ) {
window - > onHideUpdateNotifications ( hidden ) ;
}
} ) ;
if ( showProxyTab ) {
settings . showNetworkProxyTab ( ) ;
}
settings . exec ( ) ;
}
2021-05-18 15:59:18 +00:00
// ######################## WALLET OPEN ########################
void WindowManager : : tryOpenWallet ( const QString & path , const QString & password ) {
// Path : path to .keys file
QString absolutePath = path ;
if ( absolutePath . startsWith ( " ~ " ) ) {
absolutePath . replace ( 0 , 1 , QDir : : homePath ( ) ) ;
}
// If the wallet is already open, just bring window to front
for ( const auto & window : m_windows ) {
if ( absolutePath = = window - > walletKeysPath ( ) | | absolutePath = = window - > walletCachePath ( ) ) {
window - > bringToFront ( ) ;
return ;
}
}
if ( ! Utils : : fileExists ( path ) ) {
this - > handleWalletError ( QString ( " Wallet not found: %1 " ) . arg ( path ) ) ;
return ;
}
m_openingWallet = true ;
2022-06-22 19:06:32 +00:00
m_walletManager - > openWalletAsync ( path , password , constants : : networkType , constants : : kdfRounds , Utils : : ringDatabasePath ( ) ) ;
2021-05-18 15:59:18 +00:00
}
void WindowManager : : onWalletOpened ( Wallet * wallet ) {
2023-01-26 17:56:09 +00:00
if ( ! wallet ) {
QString err { " Unable to open wallet " } ;
this - > handleWalletError ( err ) ;
return ;
}
2022-03-04 12:30:26 +00:00
auto status = wallet - > status ( ) ;
if ( status ! = Wallet : : Status_Ok ) {
2021-05-18 15:59:18 +00:00
QString errMsg = wallet - > errorString ( ) ;
2022-03-04 12:30:26 +00:00
QString keysPath = wallet - > keysPath ( ) ;
QString cachePath = wallet - > cachePath ( ) ;
wallet - > deleteLater ( ) ;
if ( status = = Wallet : : Status_BadPassword ) {
2021-05-18 15:59:18 +00:00
// Don't show incorrect password when we try with empty password for the first time
bool showIncorrectPassword = m_openWalletTriedOnce ;
m_openWalletTriedOnce = true ;
2022-03-04 12:30:26 +00:00
this - > onWalletOpenPasswordRequired ( showIncorrectPassword , keysPath ) ;
2021-05-18 15:59:18 +00:00
}
else if ( errMsg = = QString ( " basic_string::_M_replace_aux " ) | | errMsg = = QString ( " std::bad_alloc " ) ) {
qCritical ( ) < < errMsg ;
2022-03-04 12:30:26 +00:00
WalletManager : : clearWalletCache ( cachePath ) ;
2021-05-18 15:59:18 +00:00
errMsg = QString ( " %1 \n \n Attempted to clean wallet cache. Please restart Feather. " ) . arg ( errMsg ) ;
this - > handleWalletError ( errMsg ) ;
} else {
this - > handleWalletError ( errMsg ) ;
}
return ;
}
2021-05-26 22:24:35 +00:00
this - > onInitialNetworkConfigured ( ) ;
2022-07-01 11:48:37 +00:00
// if (!wallet->cacheAttributeExists("feather.xmrig_password") && !wallet->cacheAttributeExists("feather.created")) {
// auto result = QMessageBox::question(nullptr, "Foreign wallet",
// "This wallet file was not created with Feather. This may cause unexpected behavior. Please restore your wallet from seed.\n\nOpen this wallet anyway?");
// if (result == QMessageBox::No) {
// wallet->deleteLater();
// this->initWizard();
// return;
// }
// }
2022-03-04 12:30:26 +00:00
2021-05-18 15:59:18 +00:00
// Create new mainwindow with wallet
m_splashDialog - > hide ( ) ;
m_openWalletTriedOnce = false ;
auto * window = new MainWindow ( this , wallet ) ;
m_windows . append ( window ) ;
this - > buildTrayMenu ( ) ;
m_openingWallet = false ;
}
void WindowManager : : onWalletOpenPasswordRequired ( bool invalidPassword , const QString & path ) {
QFileInfo fileInfo ( path ) ;
PasswordDialog dialog { fileInfo . fileName ( ) , invalidPassword } ;
switch ( dialog . exec ( ) ) {
case QDialog : : Rejected :
{
m_openWalletTriedOnce = false ;
2023-03-06 18:53:43 +00:00
if ( m_wizard ) {
m_wizard - > show ( ) ;
} else {
this - > showWizard ( WalletWizard : : Page_Menu ) ;
}
2021-05-18 15:59:18 +00:00
return ;
}
}
this - > tryOpenWallet ( path , dialog . password ) ;
}
bool WindowManager : : autoOpenWallet ( ) {
QString autoPath = config ( ) - > get ( Config : : autoOpenWalletPath ) . toString ( ) ;
if ( ! autoPath . isEmpty ( ) & & autoPath . startsWith ( QString : : number ( constants : : networkType ) ) ) {
autoPath . remove ( 0 , 1 ) ;
}
if ( ! autoPath . isEmpty ( ) & & Utils : : fileExists ( autoPath ) ) {
this - > tryOpenWallet ( autoPath , " " ) ; // TODO: get password from --password
return true ;
}
return false ;
}
// ######################## WALLET CREATION ########################
2022-02-25 15:29:33 +00:00
void WindowManager : : tryCreateWallet ( Seed seed , const QString & path , const QString & password , const QString & seedLanguage ,
2023-03-03 22:15:06 +00:00
const QString & seedOffset , const QString & subaddressLookahead , bool newWallet ) {
2021-05-18 15:59:18 +00:00
if ( Utils : : fileExists ( path ) ) {
auto err = QString ( " Failed to write wallet to path: \" %1 \" ; file already exists. " ) . arg ( path ) ;
this - > handleWalletError ( err ) ;
return ;
}
if ( seed . mnemonic . isEmpty ( ) ) {
this - > handleWalletError ( " Mnemonic seed error. Failed to write wallet. " ) ;
return ;
}
Wallet * wallet = nullptr ;
2022-05-23 21:43:33 +00:00
if ( seed . type = = Seed : : Type : : POLYSEED | | seed . type = = Seed : : Type : : TEVADOR ) {
2023-01-24 21:35:47 +00:00
wallet = m_walletManager - > createDeterministicWalletFromSpendKey ( path , password , seed . language , constants : : networkType , seed . spendKey , seed . restoreHeight , constants : : kdfRounds , seedOffset , subaddressLookahead ) ;
2021-05-18 15:59:18 +00:00
}
2022-05-23 21:43:33 +00:00
else if ( seed . type = = Seed : : Type : : MONERO ) {
2021-05-18 15:59:18 +00:00
wallet = m_walletManager - > recoveryWallet ( path , password , seed . mnemonic . join ( " " ) , seedOffset , constants : : networkType , seed . restoreHeight , constants : : kdfRounds ) ;
}
if ( ! wallet ) {
this - > handleWalletError ( " Failed to write wallet " ) ;
return ;
}
2022-05-26 18:30:39 +00:00
wallet - > setCacheAttribute ( " feather.seed " , seed . mnemonic . join ( " " ) ) ;
wallet - > setCacheAttribute ( " feather.seedoffset " , seedOffset ) ;
2023-03-03 22:15:06 +00:00
if ( newWallet ) {
wallet - > setNewWallet ( ) ;
}
2022-05-26 18:30:39 +00:00
2022-06-26 13:38:52 +00:00
this - > onWalletOpened ( wallet ) ;
2021-05-18 15:59:18 +00:00
}
2023-01-24 21:35:47 +00:00
void WindowManager : : tryCreateWalletFromDevice ( const QString & path , const QString & password , const QString & deviceName , int restoreHeight , const QString & subaddressLookahead )
2021-05-18 15:59:18 +00:00
{
if ( Utils : : fileExists ( path ) ) {
auto err = QString ( " Failed to write wallet to path: \" %1 \" ; file already exists. " ) . arg ( path ) ;
this - > handleWalletError ( err ) ;
return ;
}
m_openingWallet = true ;
2023-01-24 21:35:47 +00:00
m_walletManager - > createWalletFromDeviceAsync ( path , password , constants : : networkType , deviceName , restoreHeight , subaddressLookahead ) ;
2021-05-18 15:59:18 +00:00
}
void WindowManager : : tryCreateWalletFromKeys ( const QString & path , const QString & password , const QString & address ,
2023-01-24 21:35:47 +00:00
const QString & viewkey , const QString & spendkey , quint64 restoreHeight , const QString & subaddressLookahead ) {
2021-05-18 15:59:18 +00:00
if ( Utils : : fileExists ( path ) ) {
auto err = QString ( " Failed to write wallet to path: \" %1 \" ; file already exists. " ) . arg ( path ) ;
this - > handleWalletError ( err ) ;
return ;
}
2023-01-26 17:56:09 +00:00
Wallet * wallet ;
if ( address . isEmpty ( ) & & viewkey . isEmpty ( ) & & ! spendkey . isEmpty ( ) ) {
wallet = m_walletManager - > createDeterministicWalletFromSpendKey ( path , password , constants : : seedLanguage , constants : : networkType , spendkey , restoreHeight , constants : : kdfRounds , " " , subaddressLookahead ) ;
2021-05-18 15:59:18 +00:00
}
2023-01-26 17:56:09 +00:00
else {
if ( ! spendkey . isEmpty ( ) & & ! WalletManager : : keyValid ( spendkey , address , false , constants : : networkType ) ) {
auto err = QString ( " Failed to create wallet. Invalid spendkey provided. " ) . arg ( path ) ;
this - > handleWalletError ( err ) ;
return ;
}
2021-05-18 15:59:18 +00:00
2023-01-26 17:56:09 +00:00
if ( ! WalletManager : : addressValid ( address , constants : : networkType ) ) {
auto err = QString ( " Failed to create wallet. Invalid address provided. " ) . arg ( path ) ;
this - > handleWalletError ( err ) ;
return ;
}
2021-05-18 15:59:18 +00:00
2023-01-26 17:56:09 +00:00
if ( ! WalletManager : : keyValid ( viewkey , address , true , constants : : networkType ) ) {
auto err = QString ( " Failed to create wallet. Invalid viewkey provided. " ) . arg ( path ) ;
this - > handleWalletError ( err ) ;
return ;
}
wallet = m_walletManager - > createWalletFromKeys ( path , password , constants : : seedLanguage , constants : : networkType , address , viewkey , spendkey , restoreHeight , constants : : kdfRounds , subaddressLookahead ) ;
2021-05-18 15:59:18 +00:00
}
m_openingWallet = true ;
2023-01-26 17:56:09 +00:00
this - > onWalletOpened ( wallet ) ;
2021-05-18 15:59:18 +00:00
}
void WindowManager : : onWalletCreated ( Wallet * wallet ) {
// Currently only called when a wallet is created from device.
auto state = wallet - > status ( ) ;
if ( state ! = Wallet : : Status_Ok ) {
qDebug ( ) < < Q_FUNC_INFO < < QString ( " Wallet open error: %1 " ) . arg ( wallet - > errorString ( ) ) ;
this - > displayWalletErrorMessage ( wallet - > errorString ( ) ) ;
m_splashDialog - > hide ( ) ;
this - > showWizard ( WalletWizard : : Page_Menu ) ;
2023-04-20 14:50:36 +00:00
m_openingWallet = false ;
2021-05-18 15:59:18 +00:00
return ;
}
this - > onWalletOpened ( wallet ) ;
}
// ######################## ERROR HANDLING ########################
void WindowManager : : handleWalletError ( const QString & message ) {
qCritical ( ) < < message ;
this - > displayWalletErrorMessage ( message ) ;
this - > initWizard ( ) ;
}
void WindowManager : : displayWalletErrorMessage ( const QString & message ) {
2021-07-01 21:00:47 +00:00
QString errMsg = QString ( " Error: %1 " ) . arg ( message ) ;
2021-07-07 17:04:31 +00:00
QString link ;
2021-07-01 21:00:47 +00:00
// Ledger
2021-05-18 15:59:18 +00:00
if ( message . contains ( " No device found " ) ) {
2021-07-01 21:00:47 +00:00
errMsg + = " \n \n This wallet is backed by a Ledger hardware device. Make sure the Monero app is opened on the device. \n "
2021-05-18 15:59:18 +00:00
" You may need to restart Feather before the device can get detected. " ;
}
if ( message . contains ( " Unable to open device " ) ) {
errMsg + = " \n \n The device might be in use by a different application. " ;
2021-05-25 18:00:02 +00:00
# if defined(Q_OS_LINUX)
errMsg + = " \n \n Note: On Linux you may need to follow the instructions in the link below before the device can be opened: \n "
2021-07-01 21:00:47 +00:00
" https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues " ;
2021-07-07 17:04:31 +00:00
link = " https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues " ;
2021-07-01 21:00:47 +00:00
# endif
}
// TREZOR
if ( message . contains ( " Unable to claim libusb device " ) ) {
errMsg + = " \n \n This wallet is backed by a Trezor hardware device. Feather was unable to access the device. "
" Please make sure it is not opened by another program and try again. " ;
}
if ( message . contains ( " Cannot get a device address " ) ) {
errMsg + = " \n \n Restart the Trezor hardware device and try again. " ;
}
if ( message . contains ( " Could not connect to the device Trezor " ) | | message . contains ( " Device connect failed " ) ) {
2021-07-07 17:04:31 +00:00
errMsg + = " \n \n This wallet is backed by a Trezor hardware device. Make sure the device is connected to your computer and unlocked. " ;
2021-07-01 21:00:47 +00:00
# if defined(Q_OS_LINUX)
errMsg + = " \n \n Note: On Linux you may need to follow the instructions in the link below before the device can be opened: \n "
" https://wiki.trezor.io/Udev_rules " ;
2021-07-07 17:04:31 +00:00
link = " https://wiki.trezor.io/Udev_rules " ;
2021-05-25 18:00:02 +00:00
# endif
2021-05-18 15:59:18 +00:00
}
if ( message . contains ( " SW_CLIENT_NOT_SUPPORTED " ) ) {
2023-04-18 11:52:38 +00:00
errMsg + = " \n \n Incompatible version: upgrade your Ledger device firmware to the latest version using Ledger Live. \n "
" Then upgrade the Monero app for the Ledger device to the latest version. " ;
2021-05-18 15:59:18 +00:00
}
else if ( message . contains ( " Wrong Device Status " ) ) {
errMsg + = " \n \n The device may need to be unlocked. " ;
}
else if ( message . contains ( " Wrong Channel " ) ) {
errMsg + = " \n \n Restart the hardware device and try again. " ;
}
QMessageBox msgBox ;
msgBox . setWindowIcon ( icons ( ) - > icon ( " appicons/64x64.png " ) ) ;
msgBox . setIcon ( QMessageBox : : Warning ) ;
msgBox . setText ( errMsg ) ;
msgBox . setWindowTitle ( " Wallet error " ) ;
msgBox . setStandardButtons ( QMessageBox : : Ok ) ;
msgBox . setDefaultButton ( QMessageBox : : Ok ) ;
2021-07-07 17:04:31 +00:00
QPushButton * openLinkButton = nullptr ;
if ( ! link . isEmpty ( ) ) {
openLinkButton = msgBox . addButton ( " Open link " , QMessageBox : : ActionRole ) ;
}
2021-05-18 15:59:18 +00:00
msgBox . exec ( ) ;
2021-07-07 17:04:31 +00:00
if ( openLinkButton & & msgBox . clickedButton ( ) = = openLinkButton ) {
Utils : : externalLinkWarning ( nullptr , link ) ;
}
2021-05-18 15:59:18 +00:00
}
2023-01-26 12:12:33 +00:00
void WindowManager : : showCrashLogs ( ) {
QString crashLogPath { Config : : defaultConfigDir ( ) . path ( ) + " /crash_report.txt " } ;
QFile crashLogFile { crashLogPath } ;
if ( ! crashLogFile . exists ( ) ) {
return ;
}
bool r = crashLogFile . open ( QIODevice : : ReadOnly ) ;
if ( ! r ) {
qWarning ( ) < < " Unable to open crash log file: " < < crashLogPath ;
return ;
}
QTextStream log ( & crashLogFile ) ;
QString logString = log . readAll ( ) ;
crashLogFile . close ( ) ;
bool renamed = false ;
for ( int i = 1 ; i < 999 ; i + + ) {
QString name { QString ( " /crash_report_%1.txt " ) . arg ( QString : : number ( i ) ) } ;
if ( crashLogFile . rename ( Config : : defaultConfigDir ( ) . path ( ) + name ) ) {
renamed = true ;
break ;
}
}
if ( ! renamed ) {
crashLogFile . remove ( ) ;
}
QDialog dialog ( nullptr ) ;
dialog . setWindowTitle ( " Crash report " ) ;
QVBoxLayout layout ;
QLabel msg { " Feather encountered an unrecoverable error. \n \n Please send a copy of these logs to the developers. Logs are not automatically reported. \n " } ;
QTextEdit logs ;
logs . setText ( logString ) ;
layout . addWidget ( & msg ) ;
layout . addWidget ( & logs ) ;
QDialogButtonBox buttons ( QDialogButtonBox : : Ok ) ;
layout . addWidget ( & buttons ) ;
dialog . setLayout ( & layout ) ;
QObject : : connect ( & buttons , & QDialogButtonBox : : accepted , [ & dialog ] {
dialog . close ( ) ;
} ) ;
dialog . exec ( ) ;
exit ( 0 ) ;
}
2021-05-18 15:59:18 +00:00
// ######################## DEVICE ########################
void WindowManager : : onDeviceButtonRequest ( quint64 code ) {
2021-07-01 21:00:47 +00:00
QString message ;
switch ( code ) {
case 1 : // Trezor
message = " Action required on device: enter your PIN to continue. " ;
break ;
case 8 : // Trezor
message = " Action required on device: Export watch-only credentials to open the wallet. " ;
break ;
2021-07-08 11:21:09 +00:00
case 19 : // Trezor
message = " Action required on device: Enter passphrase to open the wallet. " ;
break ;
2021-07-01 21:00:47 +00:00
default :
message = " Action required on device: Export the view key to open the wallet. " ;
}
m_splashDialog - > setMessage ( message ) ;
2021-05-18 15:59:18 +00:00
m_splashDialog - > setIcon ( QPixmap ( " :/assets/images/key.png " ) ) ;
m_splashDialog - > show ( ) ;
m_splashDialog - > setEnabled ( true ) ;
}
2021-07-07 02:38:15 +00:00
void WindowManager : : onDeviceButtonPressed ( ) {
m_splashDialog - > hide ( ) ;
}
2021-05-18 15:59:18 +00:00
void WindowManager : : onDeviceError ( const QString & errorMessage ) {
// TODO: when does this get called?
qCritical ( ) < < Q_FUNC_INFO < < errorMessage ;
}
2021-07-08 00:34:27 +00:00
void WindowManager : : onWalletPassphraseNeeded ( bool on_device ) {
auto button = QMessageBox : : question ( nullptr , " Wallet Passphrase Needed " , " Enter passphrase on hardware wallet? \n \n "
" It is recommended to enter passphrase on "
" the hardware wallet for better security. " ,
QMessageBox : : Yes | QMessageBox : : No , QMessageBox : : Yes ) ;
if ( button = = QMessageBox : : Yes ) {
m_walletManager - > onPassphraseEntered ( " " , true , false ) ;
return ;
}
bool ok ;
QString passphrase = QInputDialog : : getText ( nullptr , " Wallet Passphrase Needed " , " Enter passphrase: " , QLineEdit : : EchoMode : : Password , " " , & ok ) ;
2023-03-03 23:10:17 +00:00
m_walletManager - > onPassphraseEntered ( passphrase , false , false ) ;
2021-07-08 00:34:27 +00:00
}
2021-05-18 15:59:18 +00:00
// ######################## TRAY ########################
void WindowManager : : buildTrayMenu ( ) {
QMenu * menu ;
if ( ! m_tray - > contextMenu ( ) ) {
menu = new QMenu ( ) ;
m_tray - > setContextMenu ( menu ) ;
} else {
menu = m_tray - > contextMenu ( ) ;
menu - > clear ( ) ;
}
for ( const auto & window : m_windows ) {
QString name = window - > walletName ( ) ;
QMenu * submenu = menu - > addMenu ( name ) ;
submenu - > addAction ( " Show/Hide " , window , & MainWindow : : showOrHide ) ;
submenu - > addAction ( " Close " , window , & MainWindow : : close ) ;
}
menu - > addSeparator ( ) ;
menu - > addAction ( " Exit Feather " , this , & WindowManager : : close ) ;
}
2023-03-29 08:46:53 +00:00
void WindowManager : : notify ( const QString & title , const QString & message , int duration ) {
if ( ! m_tray | | ! QSystemTrayIcon : : supportsMessages ( ) ) {
return ;
}
if ( config ( ) - > get ( Config : : hideNotifications ) . toBool ( ) ) {
return ;
}
m_tray - > showMessage ( title , message , icons ( ) - > icon ( " appicons/64x64.png " ) , duration ) ;
}
2021-05-18 15:59:18 +00:00
// ######################## NETWORKING ########################
void WindowManager : : onInitialNetworkConfigured ( ) {
2021-05-26 22:24:35 +00:00
if ( ! m_initialNetworkConfigured ) {
m_initialNetworkConfigured = true ;
2021-05-27 02:50:58 +00:00
appData ( ) ;
2022-03-14 21:36:58 +00:00
2023-02-11 17:11:21 +00:00
this - > onProxySettingsChanged ( ) ;
2021-05-26 22:24:35 +00:00
}
2021-05-18 15:59:18 +00:00
}
2023-02-11 17:11:21 +00:00
void WindowManager : : onProxySettingsChanged ( ) {
if ( Utils : : isTorsocks ( ) ) {
return ;
}
// Will kill the process if necessary
2021-05-18 15:59:18 +00:00
torManager ( ) - > init ( ) ;
torManager ( ) - > start ( ) ;
2023-02-11 17:11:21 +00:00
QNetworkProxy proxy { QNetworkProxy : : NoProxy } ;
if ( config ( ) - > get ( Config : : proxy ) . toInt ( ) ! = Config : : Proxy : : None ) {
QString host = config ( ) - > get ( Config : : socks5Host ) . toString ( ) ;
quint16 port = config ( ) - > get ( Config : : socks5Port ) . toString ( ) . toUShort ( ) ;
2021-05-18 15:59:18 +00:00
2023-02-11 17:11:21 +00:00
if ( config ( ) - > get ( Config : : proxy ) . toInt ( ) = = Config : : Proxy : : Tor & & ! torManager ( ) - > isLocalTor ( ) ) {
host = torManager ( ) - > featherTorHost ;
port = torManager ( ) - > featherTorPort ;
}
2021-05-18 15:59:18 +00:00
2023-02-11 17:11:21 +00:00
proxy = QNetworkProxy { QNetworkProxy : : Socks5Proxy , host , port } ;
getNetworkSocks5 ( ) - > setProxy ( proxy ) ;
2021-05-18 15:59:18 +00:00
}
2023-02-11 17:11:21 +00:00
qWarning ( ) < < " Proxy: " < < proxy . hostName ( ) < < " " < < proxy . port ( ) ;
// Switch websocket to new proxy and update URL
2023-03-01 12:28:01 +00:00
websocketNotifier ( ) - > websocketClient - > stop ( ) ;
websocketNotifier ( ) - > websocketClient - > webSocket - > setProxy ( proxy ) ;
websocketNotifier ( ) - > websocketClient - > nextWebsocketUrl ( ) ;
websocketNotifier ( ) - > websocketClient - > restart ( ) ;
2021-05-18 15:59:18 +00:00
2023-02-11 17:11:21 +00:00
emit proxySettingsChanged ( ) ;
2021-05-18 15:59:18 +00:00
}
2022-03-15 12:36:20 +00:00
void WindowManager : : onWebsocketStatusChanged ( bool enabled ) {
emit websocketStatusChanged ( enabled ) ;
}
2021-05-18 15:59:18 +00:00
// ######################## WIZARD ########################
2023-02-11 17:11:21 +00:00
WalletWizard * WindowManager : : createWizard ( WalletWizard : : Page startPage ) {
2021-05-18 15:59:18 +00:00
auto * wizard = new WalletWizard ;
connect ( wizard , & WalletWizard : : initialNetworkConfigured , this , & WindowManager : : onInitialNetworkConfigured ) ;
2023-02-11 17:11:21 +00:00
connect ( wizard , & WalletWizard : : showSettings , [ this , wizard ] {
this - > showSettings ( nullptr , wizard ) ;
} ) ;
2021-05-18 15:59:18 +00:00
connect ( wizard , & WalletWizard : : openWallet , this , & WindowManager : : tryOpenWallet ) ;
connect ( wizard , & WalletWizard : : createWallet , this , & WindowManager : : tryCreateWallet ) ;
connect ( wizard , & WalletWizard : : createWalletFromKeys , this , & WindowManager : : tryCreateWalletFromKeys ) ;
connect ( wizard , & WalletWizard : : createWalletFromDevice , this , & WindowManager : : tryCreateWalletFromDevice ) ;
return wizard ;
}
void WindowManager : : initWizard ( ) {
auto startPage = WalletWizard : : Page_Menu ;
if ( config ( ) - > get ( Config : : firstRun ) . toBool ( ) & & ! ( TailsOS : : detect ( ) | | WhonixOS : : detect ( ) ) ) {
startPage = WalletWizard : : Page_Network ;
}
this - > showWizard ( startPage ) ;
}
void WindowManager : : showWizard ( WalletWizard : : Page startPage ) {
if ( ! m_wizard ) {
m_wizard = this - > createWizard ( startPage ) ;
}
2022-05-25 10:59:28 +00:00
m_wizard - > resetFields ( ) ;
2021-05-18 15:59:18 +00:00
m_wizard - > setStartId ( startPage ) ;
m_wizard - > restart ( ) ;
m_wizard - > setEnabled ( true ) ;
m_wizard - > show ( ) ;
}
void WindowManager : : wizardOpenWallet ( ) {
this - > showWizard ( WalletWizard : : Page_OpenWallet ) ;
}
// ######################## SKINS ########################
void WindowManager : : initSkins ( ) {
m_skins . insert ( " Native " , " " ) ;
QString qdarkstyle = this - > loadStylesheet ( " :qdarkstyle/style.qss " ) ;
if ( ! qdarkstyle . isEmpty ( ) )
m_skins . insert ( " QDarkStyle " , qdarkstyle ) ;
QString breeze_dark = this - > loadStylesheet ( " :/dark.qss " ) ;
if ( ! breeze_dark . isEmpty ( ) )
m_skins . insert ( " Breeze/Dark " , breeze_dark ) ;
QString breeze_light = this - > loadStylesheet ( " :/light.qss " ) ;
if ( ! breeze_light . isEmpty ( ) )
m_skins . insert ( " Breeze/Light " , breeze_light ) ;
QString skin = config ( ) - > get ( Config : : skin ) . toString ( ) ;
qApp - > setStyleSheet ( m_skins [ skin ] ) ;
}
QString WindowManager : : loadStylesheet ( const QString & resource ) {
QFile f ( resource ) ;
if ( ! f . exists ( ) ) {
printf ( " Unable to set stylesheet, file not found \n " ) ;
f . close ( ) ;
return " " ;
}
f . open ( QFile : : ReadOnly | QFile : : Text ) ;
QTextStream ts ( & f ) ;
QString data = ts . readAll ( ) ;
f . close ( ) ;
return data ;
}
2023-02-11 17:11:21 +00:00
void WindowManager : : onChangeTheme ( const QString & skinName ) {
2021-05-18 15:59:18 +00:00
if ( ! m_skins . contains ( skinName ) ) {
qWarning ( ) < < QString ( " No such skin %1 " ) . arg ( skinName ) ;
return ;
}
config ( ) - > set ( Config : : skin , skinName ) ;
2023-02-11 17:11:21 +00:00
2021-05-18 15:59:18 +00:00
qApp - > setStyleSheet ( m_skins [ skinName ] ) ;
qDebug ( ) < < QString ( " Skin changed to %1 " ) . arg ( skinName ) ;
2023-02-11 17:11:21 +00:00
this - > patchMacStylesheet ( ) ;
for ( const auto & window : m_windows ) {
window - > skinChanged ( skinName ) ;
}
2021-05-18 15:59:18 +00:00
}
2023-02-11 17:11:21 +00:00
void WindowManager : : patchMacStylesheet ( ) {
# if defined(Q_OS_MACOS)
QString styleSheet = qApp - > styleSheet ( ) ;
auto patch = Utils : : fileOpenQRC ( " :assets/macStylesheet.patch " ) ;
auto patch_text = Utils : : barrayToString ( patch ) ;
styleSheet + = patch_text ;
qApp - > setStyleSheet ( styleSheet ) ;
# endif
}