2022-02-08 17:02:51 +01:00
import re
2022-03-30 19:31:14 +02:00
import queue
import time
2022-04-07 17:45:48 +02:00
import os
2023-01-10 17:01:54 +01:00
import sys
import html
import threading
2023-03-29 21:23:00 +00:00
from typing import TYPE_CHECKING , Set
2022-02-08 17:02:51 +01:00
2023-11-10 11:11:51 +01:00
from PyQt6 . QtCore import ( pyqtSlot , pyqtSignal , pyqtProperty , QObject , QT_VERSION_STR , PYQT_VERSION_STR ,
2023-01-19 13:40:03 +01:00
qInstallMessageHandler , QTimer , QSortFilterProxyModel )
2024-02-05 21:25:13 +01:00
from PyQt6 . QtGui import QGuiApplication , QFontDatabase
from PyQt6 . QtQml import qmlRegisterType , QQmlApplicationEngine
2022-02-08 17:02:51 +01:00
2024-02-03 05:13:09 +00:00
import electrum
2023-01-10 17:01:54 +01:00
from electrum import version , constants
from electrum . i18n import _
2022-10-18 18:31:59 +02:00
from electrum . logging import Logger , get_logger
2023-07-08 12:16:43 +02:00
from electrum . bip21 import BITCOIN_BIP21_URI_SCHEME , LIGHTNING_URI_SCHEME
2023-01-10 17:17:19 +01:00
from electrum . base_crash_reporter import BaseCrashReporter , EarlyExceptionsQueue
2023-01-10 17:01:54 +01:00
from electrum . network import Network
2023-07-11 12:51:37 +02:00
from electrum . plugin import run_hook
2022-02-08 17:02:51 +01:00
from . qeconfig import QEConfig
2022-11-11 12:00:07 +01:00
from . qedaemon import QEDaemon
2022-02-08 17:02:51 +01:00
from . qenetwork import QENetwork
from . qewallet import QEWallet
2022-07-21 10:19:07 +02:00
from . qeqr import QEQRParser , QEQRImageProvider , QEQRImageProviderHelper
2023-10-11 11:02:40 +00:00
from . qeqrscanner import QEQRScanner
2022-02-28 19:40:57 +01:00
from . qebitcoin import QEBitcoin
2022-04-05 13:57:42 +02:00
from . qefx import QEFX
2024-09-17 13:22:05 +02:00
from . qetxfinalizer import QETxFinalizer , QETxRbfFeeBumper , QETxCpfpFeeBumper , QETxCanceller , QETxSweepFinalizer
2023-04-04 16:13:00 +02:00
from . qeinvoice import QEInvoice , QEInvoiceParser
2022-08-23 17:13:22 +02:00
from . qerequestdetails import QERequestDetails
2022-04-25 15:55:34 +02:00
from . qetypes import QEAmount
2022-05-04 15:01:50 +02:00
from . qeaddressdetails import QEAddressDetails
2022-05-10 13:26:48 +02:00
from . qetxdetails import QETxDetails
2022-05-12 16:53:44 +02:00
from . qechannelopener import QEChannelOpener
2022-06-13 19:00:31 +02:00
from . qelnpaymentdetails import QELnPaymentDetails
2022-06-22 11:36:25 +02:00
from . qechanneldetails import QEChannelDetails
2022-06-30 15:06:45 +02:00
from . qeswaphelper import QESwapHelper
2022-10-04 19:47:29 +02:00
from . qewizard import QENewWalletWizard , QEServerConnectWizard
2023-01-19 13:40:03 +01:00
from . qemodelfilter import QEFilterProxyModel
2023-05-08 13:36:45 +02:00
from . qebip39recovery import QEBip39RecoveryListModel
2022-02-08 17:02:51 +01:00
2023-01-10 17:32:20 +01:00
if TYPE_CHECKING :
from electrum . simple_config import SimpleConfig
from electrum . wallet import Abstract_Wallet
2023-03-28 15:24:40 +00:00
from electrum . daemon import Daemon
from electrum . plugin import Plugins
2023-01-10 17:32:20 +01:00
2023-04-03 16:07:15 +02:00
if ' ANDROID_DATA ' in os . environ :
from jnius import autoclass , cast
from android import activity
jpythonActivity = autoclass ( ' org.kivy.android.PythonActivity ' ) . mActivity
jHfc = autoclass ( ' android.view.HapticFeedbackConstants ' )
jString = autoclass ( ' java.lang.String ' )
jIntent = autoclass ( ' android.content.Intent ' )
jview = jpythonActivity . getWindow ( ) . getDecorView ( )
2022-04-07 17:45:48 +02:00
notification = None
2023-08-14 16:24:48 +02:00
2023-01-10 17:01:54 +01:00
class QEAppController ( BaseCrashReporter , QObject ) :
_dummy = pyqtSignal ( )
2023-02-07 13:51:26 +01:00
userNotify = pyqtSignal ( str , str )
2022-10-18 18:31:59 +02:00
uriReceived = pyqtSignal ( str )
2023-02-09 01:34:54 +01:00
showException = pyqtSignal ( ' QVariantMap ' )
2023-01-10 17:01:54 +01:00
sendingBugreport = pyqtSignal ( )
sendingBugreportSuccess = pyqtSignal ( str )
sendingBugreportFailure = pyqtSignal ( str )
2023-04-29 13:45:28 +02:00
secureWindowChanged = pyqtSignal ( )
2023-07-14 13:51:08 +02:00
wantCloseChanged = pyqtSignal ( )
2022-03-30 19:31:14 +02:00
2023-07-11 12:51:37 +02:00
def __init__ ( self , qeapp : ' ElectrumQmlApplication ' , qedaemon : ' QEDaemon ' , plugins : ' Plugins ' ) :
2023-01-10 17:01:54 +01:00
BaseCrashReporter . __init__ ( self , None , None , None )
QObject . __init__ ( self )
2022-03-30 19:31:14 +02:00
2023-07-11 12:51:37 +02:00
self . _app = qeapp
2022-03-30 19:31:14 +02:00
self . _qedaemon = qedaemon
2022-09-08 12:19:38 +02:00
self . _plugins = plugins
2023-03-29 21:23:00 +00:00
self . config = qedaemon . daemon . config
2022-03-30 19:31:14 +02:00
2023-02-27 12:20:51 +01:00
self . _crash_user_text = ' '
self . _app_started = False
self . _intent = ' '
2023-04-29 13:45:28 +02:00
self . _secureWindow = False
2023-02-27 12:20:51 +01:00
2022-03-30 19:31:14 +02:00
# set up notification queue and notification_timer
self . user_notification_queue = queue . Queue ( )
self . user_notification_last_time = 0
self . notification_timer = QTimer ( self )
self . notification_timer . setSingleShot ( False )
self . notification_timer . setInterval ( 500 ) # msec
self . notification_timer . timeout . connect ( self . on_notification_timer )
self . _qedaemon . walletLoaded . connect ( self . on_wallet_loaded )
2023-04-03 16:07:15 +02:00
self . userNotify . connect ( self . doNotify )
2022-04-07 17:45:48 +02:00
2023-04-03 16:07:15 +02:00
if self . isAndroid ( ) :
self . bindIntent ( )
2022-10-18 18:31:59 +02:00
2023-07-14 13:51:08 +02:00
self . _want_close = False
2022-03-30 19:31:14 +02:00
def on_wallet_loaded ( self ) :
qewallet = self . _qedaemon . currentWallet
2022-06-21 14:11:03 +02:00
if not qewallet :
return
2023-03-03 12:26:36 +01:00
# register wallet in Exception_Hook
Exception_Hook . maybe_setup ( config = qewallet . wallet . config , wallet = qewallet . wallet )
2022-03-30 19:31:14 +02:00
# attach to the wallet user notification events
# connect only once
try :
qewallet . userNotify . disconnect ( self . on_wallet_usernotify )
2023-04-23 01:33:12 +00:00
except Exception :
2022-03-30 19:31:14 +02:00
pass
qewallet . userNotify . connect ( self . on_wallet_usernotify )
def on_wallet_usernotify ( self , wallet , message ) :
self . logger . debug ( message )
2023-02-07 13:51:26 +01:00
self . user_notification_queue . put ( ( wallet , message ) )
2022-03-30 19:31:14 +02:00
if not self . notification_timer . isActive ( ) :
self . logger . debug ( ' starting app notification timer ' )
self . notification_timer . start ( )
def on_notification_timer ( self ) :
if self . user_notification_queue . qsize ( ) == 0 :
self . logger . debug ( ' queue empty, stopping app notification timer ' )
self . notification_timer . stop ( )
return
now = time . time ( )
rate_limit = 20 # seconds
if self . user_notification_last_time + rate_limit > now :
return
self . user_notification_last_time = now
self . logger . info ( " Notifying GUI about new user notifications " )
try :
2023-02-07 13:51:26 +01:00
wallet , message = self . user_notification_queue . get_nowait ( )
self . userNotify . emit ( str ( wallet ) , message )
2022-03-30 19:31:14 +02:00
except queue . Empty :
pass
2023-04-03 16:07:15 +02:00
def doNotify ( self , wallet_name , message ) :
2024-09-19 16:46:07 +00:00
self . logger . debug ( f ' sending push notification to OS: { message =!r} ' )
# FIXME: this does not work on Android 13+. We would need to declare (in manifest)
# and also request-at-runtime android.permission.POST_NOTIFICATIONS.
2022-04-07 17:45:48 +02:00
try :
# TODO: lazy load not in UI thread please
global notification
if not notification :
from plyer import notification
2024-10-10 15:42:39 +00:00
icon = os . path . join (
os . path . dirname ( os . path . dirname ( os . path . realpath ( __file__ ) ) ) , " icons " , " electrum.png " ,
)
2022-04-07 17:45:48 +02:00
notification . notify ( ' Electrum ' , message , app_icon = icon , app_name = ' Electrum ' )
except ImportError :
2022-07-12 18:58:36 +02:00
self . logger . warning ( ' Notification: needs plyer; `sudo python3 -m pip install plyer` ' )
except Exception as e :
self . logger . error ( repr ( e ) )
2022-04-07 17:45:48 +02:00
2022-10-18 18:31:59 +02:00
def bindIntent ( self ) :
2023-03-23 14:34:47 +00:00
if not self . isAndroid ( ) :
return
2022-10-18 18:31:59 +02:00
try :
2023-04-03 16:07:15 +02:00
self . on_new_intent ( jpythonActivity . getIntent ( ) )
2022-10-18 18:31:59 +02:00
activity . bind ( on_new_intent = self . on_new_intent )
except Exception as e :
self . logger . error ( f ' unable to bind intent: { repr ( e ) } ' )
def on_new_intent ( self , intent ) :
2023-02-27 12:20:51 +01:00
if not self . _app_started :
self . _intent = intent
return
2022-10-18 18:31:59 +02:00
data = str ( intent . getDataString ( ) )
2023-02-27 12:20:51 +01:00
self . logger . debug ( f ' received intent: { repr ( data ) } ' )
2022-10-18 18:31:59 +02:00
scheme = str ( intent . getScheme ( ) ) . lower ( )
if scheme == BITCOIN_BIP21_URI_SCHEME or scheme == LIGHTNING_URI_SCHEME :
self . uriReceived . emit ( data )
2023-02-27 12:20:51 +01:00
def startupFinished ( self ) :
self . _app_started = True
if self . _intent :
self . on_new_intent ( self . _intent )
2023-07-14 13:51:08 +02:00
@pyqtProperty ( bool , notify = wantCloseChanged )
def wantClose ( self ) :
return self . _want_close
@wantClose.setter
def wantClose ( self , want_close ) :
if want_close != self . _want_close :
self . _want_close = want_close
self . wantCloseChanged . emit ( )
2022-05-10 14:29:43 +02:00
@pyqtSlot ( str , str )
def doShare ( self , data , title ) :
2023-04-03 16:07:15 +02:00
if not self . isAndroid ( ) :
2022-05-10 14:29:43 +02:00
return
2023-04-03 16:07:15 +02:00
sendIntent = jIntent ( )
sendIntent . setAction ( jIntent . ACTION_SEND )
2022-05-10 14:29:43 +02:00
sendIntent . setType ( " text/plain " )
2023-04-03 16:07:15 +02:00
sendIntent . putExtra ( jIntent . EXTRA_TEXT , jString ( data ) )
it = jIntent . createChooser ( sendIntent , cast ( ' java.lang.CharSequence ' , jString ( title ) ) )
jpythonActivity . startActivity ( it )
2022-05-10 14:29:43 +02:00
2024-11-23 21:44:02 -08:00
@pyqtSlot ( )
def isMaxBrightnessOnQrDisplayEnabled ( self ) :
return self . config . GUI_QML_SET_MAX_BRIGHTNESS_ON_QR_DISPLAY
2024-08-21 23:37:36 +00:00
@pyqtSlot ( )
def setMaxScreenBrightness ( self ) :
self . _set_screen_brightness ( 1.0 )
@pyqtSlot ( )
def resetScreenBrightness ( self ) :
self . _set_screen_brightness ( - 1.0 )
def _set_screen_brightness ( self , br : float ) - > None :
""" br is the desired screen brightness, a value in the [0, 1] interval.
A negative value, e.g. -1.0, means a " reset " back to the system preferred value.
"""
if not self . isAndroid ( ) :
return
from android . runnable import run_on_ui_thread
@run_on_ui_thread
def set_br ( ) :
window = jpythonActivity . getWindow ( )
attrs = window . getAttributes ( )
attrs . screenBrightness = br
window . setAttributes ( attrs )
set_br ( )
2022-03-30 19:31:14 +02:00
@pyqtSlot ( ' QString ' )
def textToClipboard ( self , text ) :
QGuiApplication . clipboard ( ) . setText ( text )
2022-04-12 16:48:32 +02:00
@pyqtSlot ( result = ' QString ' )
def clipboardToText ( self ) :
2024-10-23 16:09:28 +02:00
clip = QGuiApplication . clipboard ( )
return clip . text ( ) if clip . mimeData ( ) . hasText ( ) else ' '
2022-04-12 16:48:32 +02:00
2022-09-08 12:19:38 +02:00
@pyqtSlot ( str , result = QObject )
def plugin ( self , plugin_name ) :
2022-09-08 15:15:46 +02:00
self . logger . debug ( f ' now { self . _plugins . count ( ) } plugins loaded ' )
2022-09-08 12:19:38 +02:00
plugin = self . _plugins . get ( plugin_name )
self . logger . debug ( f ' plugin with name { plugin_name } is { str ( type ( plugin ) ) } ' )
2024-02-05 21:25:13 +01:00
if plugin and hasattr ( plugin , ' so ' ) :
2022-09-08 12:19:38 +02:00
return plugin . so
else :
self . logger . debug ( ' None! ' )
return None
2022-09-08 15:15:46 +02:00
@pyqtProperty ( ' QVariant ' , notify = _dummy )
def plugins ( self ) :
s = [ ]
for item in self . _plugins . descriptions :
self . logger . info ( item )
s . append ( {
' name ' : item ,
' fullname ' : self . _plugins . descriptions [ item ] [ ' fullname ' ] ,
' enabled ' : bool ( self . _plugins . get ( item ) )
} )
self . logger . debug ( f ' { str ( s ) } ' )
return s
@pyqtSlot ( str , bool )
2023-07-11 12:51:37 +02:00
def setPluginEnabled ( self , plugin : str , enabled : bool ) :
2022-09-08 15:15:46 +02:00
if enabled :
self . _plugins . enable ( plugin )
2023-07-11 12:51:37 +02:00
# note: all enabled plugins will receive this hook:
run_hook ( ' init_qml ' , self . _app )
2022-09-08 15:15:46 +02:00
else :
self . _plugins . disable ( plugin )
2023-07-11 12:51:37 +02:00
@pyqtSlot ( str , result = bool )
def isPluginEnabled ( self , plugin : str ) :
return bool ( self . _plugins . get ( plugin ) )
2023-01-06 13:41:47 +01:00
@pyqtSlot ( result = bool )
def isAndroid ( self ) :
return ' ANDROID_DATA ' in os . environ
2022-09-08 15:15:46 +02:00
2023-01-10 17:01:54 +01:00
@pyqtSlot ( result = ' QVariantMap ' )
def crashData ( self ) :
return {
' traceback ' : self . get_traceback_info ( ) ,
' extra ' : self . get_additional_info ( ) ,
' reportstring ' : self . get_report_string ( )
}
2024-02-05 21:25:13 +01:00
@pyqtSlot ( object , object , object , object )
2023-01-10 17:01:54 +01:00
def crash ( self , config , e , text , tb ) :
2024-02-05 21:25:13 +01:00
self . exc_args = ( e , text , tb ) # for BaseCrashReporter
2023-02-09 01:34:54 +01:00
self . showException . emit ( self . crashData ( ) )
2023-01-10 17:01:54 +01:00
@pyqtSlot ( )
def sendReport ( self ) :
network = Network . get_instance ( )
proxy = network . proxy
def report_task ( ) :
try :
response = BaseCrashReporter . send_report ( self , network . asyncio_loop , proxy )
except Exception as e :
self . logger . error ( ' There was a problem with the automatic reporting ' , exc_info = e )
self . sendingBugreportFailure . emit ( _ ( ' There was a problem with the automatic reporting: ' ) + ' <br/> ' +
repr ( e ) [ : 120 ] + ' <br/><br/> ' +
_ ( " Please report this issue manually " ) +
f ' <a href= " { constants . GIT_REPO_ISSUES_URL } " >on GitHub</a>. ' )
2023-03-16 19:07:33 +00:00
else :
text = response . text
if response . url :
text + = f " You can track further progress on <a href= ' { response . url } ' >GitHub</a>. "
self . sendingBugreportSuccess . emit ( text )
2023-01-10 17:01:54 +01:00
self . sendingBugreport . emit ( )
2023-04-05 12:26:32 +02:00
threading . Thread ( target = report_task , daemon = True ) . start ( )
2023-01-10 17:01:54 +01:00
@pyqtSlot ( )
def showNever ( self ) :
2023-05-24 17:41:44 +00:00
self . config . SHOW_CRASH_REPORTER = False
2023-01-10 17:01:54 +01:00
@pyqtSlot ( str )
def setCrashUserText ( self , text ) :
self . _crash_user_text = text
def _get_traceback_str_to_display ( self ) - > str :
# The msg_box that shows the report uses rich_text=True, so
# if traceback contains special HTML characters, e.g. '<',
# they need to be escaped to avoid formatting issues.
traceback_str = super ( ) . _get_traceback_str_to_display ( )
2024-02-05 21:25:13 +01:00
return html . escape ( traceback_str ) . replace ( ' ' ' , ' ' ' )
2023-01-10 17:01:54 +01:00
def get_user_description ( self ) :
return self . _crash_user_text
def get_wallet_type ( self ) :
wallet_types = Exception_Hook . _INSTANCE . wallet_types_seen
return " , " . join ( wallet_types )
2023-04-03 16:07:15 +02:00
@pyqtSlot ( )
def haptic ( self ) :
if not self . isAndroid ( ) :
return
2023-04-05 12:28:56 +02:00
jview . performHapticFeedback ( jHfc . VIRTUAL_KEY )
2023-04-03 16:07:15 +02:00
2023-04-29 13:45:28 +02:00
@pyqtProperty ( bool , notify = secureWindowChanged )
def secureWindow ( self ) :
return self . _secureWindow
@secureWindow.setter
def secureWindow ( self , secure ) :
if not self . isAndroid ( ) :
return
2023-12-27 07:28:39 +00:00
if self . config . GUI_QML_ALWAYS_ALLOW_SCREENSHOTS :
return
2023-04-29 13:45:28 +02:00
if self . _secureWindow != secure :
jpythonActivity . setSecureWindow ( secure )
self . _secureWindow = secure
self . secureWindowChanged . emit ( )
2023-04-03 16:07:15 +02:00
2023-08-14 16:24:48 +02:00
2022-02-08 17:02:51 +01:00
class ElectrumQmlApplication ( QGuiApplication ) :
2022-03-30 19:31:14 +02:00
_valid = True
2022-02-08 17:02:51 +01:00
2023-03-28 15:24:40 +00:00
def __init__ ( self , args , * , config : ' SimpleConfig ' , daemon : ' Daemon ' , plugins : ' Plugins ' ) :
2022-02-08 17:02:51 +01:00
super ( ) . __init__ ( args )
self . logger = get_logger ( __name__ )
ElectrumQmlApplication . _daemon = daemon
2023-07-14 13:51:08 +02:00
# TODO QT6 order of declaration is important now?
qmlRegisterType ( QEAmount , ' org.electrum ' , 1 , 0 , ' Amount ' )
qmlRegisterType ( QENewWalletWizard , ' org.electrum ' , 1 , 0 , ' QNewWalletWizard ' )
qmlRegisterType ( QEServerConnectWizard , ' org.electrum ' , 1 , 0 , ' QServerConnectWizard ' )
qmlRegisterType ( QEFilterProxyModel , ' org.electrum ' , 1 , 0 , ' FilterProxyModel ' )
qmlRegisterType ( QSortFilterProxyModel , ' org.electrum ' , 1 , 0 , ' QSortFilterProxyModel ' )
2022-02-08 17:02:51 +01:00
qmlRegisterType ( QEWallet , ' org.electrum ' , 1 , 0 , ' Wallet ' )
2022-02-28 19:40:57 +01:00
qmlRegisterType ( QEBitcoin , ' org.electrum ' , 1 , 0 , ' Bitcoin ' )
2022-03-30 18:01:16 +02:00
qmlRegisterType ( QEQRParser , ' org.electrum ' , 1 , 0 , ' QRParser ' )
2023-10-11 11:02:40 +00:00
qmlRegisterType ( QEQRScanner , ' org.electrum ' , 1 , 0 , ' QRScanner ' )
2022-04-05 13:57:42 +02:00
qmlRegisterType ( QEFX , ' org.electrum ' , 1 , 0 , ' FX ' )
2022-04-12 16:48:32 +02:00
qmlRegisterType ( QETxFinalizer , ' org.electrum ' , 1 , 0 , ' TxFinalizer ' )
2022-04-22 12:16:57 +02:00
qmlRegisterType ( QEInvoice , ' org.electrum ' , 1 , 0 , ' Invoice ' )
2022-06-27 23:00:58 +02:00
qmlRegisterType ( QEInvoiceParser , ' org.electrum ' , 1 , 0 , ' InvoiceParser ' )
2022-05-04 15:01:50 +02:00
qmlRegisterType ( QEAddressDetails , ' org.electrum ' , 1 , 0 , ' AddressDetails ' )
2022-05-10 13:26:48 +02:00
qmlRegisterType ( QETxDetails , ' org.electrum ' , 1 , 0 , ' TxDetails ' )
2022-05-12 16:53:44 +02:00
qmlRegisterType ( QEChannelOpener , ' org.electrum ' , 1 , 0 , ' ChannelOpener ' )
2022-06-13 19:00:31 +02:00
qmlRegisterType ( QELnPaymentDetails , ' org.electrum ' , 1 , 0 , ' LnPaymentDetails ' )
2022-06-22 11:36:25 +02:00
qmlRegisterType ( QEChannelDetails , ' org.electrum ' , 1 , 0 , ' ChannelDetails ' )
2022-06-30 15:06:45 +02:00
qmlRegisterType ( QESwapHelper , ' org.electrum ' , 1 , 0 , ' SwapHelper ' )
2022-08-23 17:13:22 +02:00
qmlRegisterType ( QERequestDetails , ' org.electrum ' , 1 , 0 , ' RequestDetails ' )
2022-11-23 10:21:07 +01:00
qmlRegisterType ( QETxRbfFeeBumper , ' org.electrum ' , 1 , 0 , ' TxRbfFeeBumper ' )
2022-11-23 17:07:41 +01:00
qmlRegisterType ( QETxCpfpFeeBumper , ' org.electrum ' , 1 , 0 , ' TxCpfpFeeBumper ' )
2022-11-22 13:37:46 +01:00
qmlRegisterType ( QETxCanceller , ' org.electrum ' , 1 , 0 , ' TxCanceller ' )
2024-09-17 13:22:05 +02:00
qmlRegisterType ( QETxSweepFinalizer , ' org.electrum ' , 1 , 0 , ' SweepFinalizer ' )
2023-05-08 13:36:45 +02:00
qmlRegisterType ( QEBip39RecoveryListModel , ' org.electrum ' , 1 , 0 , ' Bip39RecoveryListModel ' )
2022-02-08 17:02:51 +01:00
2023-07-19 14:13:10 +02:00
# TODO QT6: these were declared as uncreatable, but that doesn't seem to work for pyqt6
2023-07-14 13:51:08 +02:00
# qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')
# qmlRegisterUncreatableType(QENewWalletWizard, 'org.electrum', 1, 0, 'QNewWalletWizard', 'QNewWalletWizard can only be used as property')
# qmlRegisterUncreatableType(QEServerConnectWizard, 'org.electrum', 1, 0, 'QServerConnectWizard', 'QServerConnectWizard can only be used as property')
# qmlRegisterUncreatableType(QEFilterProxyModel, 'org.electrum', 1, 0, 'FilterProxyModel', 'FilterProxyModel can only be used as property')
# qmlRegisterUncreatableType(QSortFilterProxyModel, 'org.electrum', 1, 0, 'QSortFilterProxyModel', 'QSortFilterProxyModel can only be used as property')
2022-04-25 15:55:34 +02:00
2022-02-08 17:02:51 +01:00
self . engine = QQmlApplicationEngine ( parent = self )
2022-06-14 17:24:50 +02:00
screensize = self . primaryScreen ( ) . size ( )
2023-07-19 14:13:10 +02:00
qr_size = min ( screensize . width ( ) , screensize . height ( ) ) * 7 / 8
self . qr_ip = QEQRImageProvider ( qr_size )
2022-03-18 15:00:29 +01:00
self . engine . addImageProvider ( ' qrgen ' , self . qr_ip )
2023-07-19 14:13:10 +02:00
self . qr_ip_h = QEQRImageProviderHelper ( qr_size )
2022-03-18 15:00:29 +01:00
2022-03-24 21:19:29 +01:00
# add a monospace font as we can't rely on device having one
2022-03-25 09:29:58 +01:00
self . fixedFont = ' PT Mono '
2022-05-11 12:22:31 +02:00
not_loaded = QFontDatabase . addApplicationFont ( ' electrum/gui/qml/fonts/PTMono-Regular.ttf ' ) < 0
not_loaded = QFontDatabase . addApplicationFont ( ' electrum/gui/qml/fonts/PTMono-Bold.ttf ' ) < 0 and not_loaded
if not_loaded :
self . logger . warning ( ' Could not load font PT Mono ' )
self . fixedFont = ' Monospace ' # hope for the best
2022-03-24 21:19:29 +01:00
2022-02-08 17:02:51 +01:00
self . context = self . engine . rootContext ( )
2022-09-08 15:15:46 +02:00
self . plugins = plugins
2022-03-30 19:31:14 +02:00
self . _qeconfig = QEConfig ( config )
2022-07-22 12:49:09 +02:00
self . _qenetwork = QENetwork ( daemon . network , self . _qeconfig )
2023-08-14 16:24:48 +02:00
self . daemon = QEDaemon ( daemon , self . plugins )
2023-07-11 12:51:37 +02:00
self . appController = QEAppController ( self , self . daemon , self . plugins )
2022-05-12 16:53:44 +02:00
self . _maxAmount = QEAmount ( is_max = True )
2022-09-08 12:19:38 +02:00
self . context . setContextProperty ( ' AppController ' , self . appController )
2022-03-30 19:31:14 +02:00
self . context . setContextProperty ( ' Config ' , self . _qeconfig )
self . context . setContextProperty ( ' Network ' , self . _qenetwork )
2022-09-08 12:19:38 +02:00
self . context . setContextProperty ( ' Daemon ' , self . daemon )
2022-03-24 21:19:29 +01:00
self . context . setContextProperty ( ' FixedFont ' , self . fixedFont )
2022-05-12 16:53:44 +02:00
self . context . setContextProperty ( ' MAX ' , self . _maxAmount )
2022-07-21 10:19:07 +02:00
self . context . setContextProperty ( ' QRIP ' , self . qr_ip_h )
2022-04-01 16:19:04 +02:00
self . context . setContextProperty ( ' BUILD ' , {
' electrum_version ' : version . ELECTRUM_VERSION ,
2023-11-10 11:11:51 +01:00
' protocol_version ' : version . PROTOCOL_VERSION ,
' qt_version ' : QT_VERSION_STR ,
' pyqt_version ' : PYQT_VERSION_STR
2022-04-01 16:19:04 +02:00
} )
2024-02-03 05:13:09 +00:00
self . context . setContextProperty ( ' UI_UNIT_NAME ' , {
" FEERATE_SAT_PER_VBYTE " : electrum . util . UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE ,
" FEERATE_SAT_PER_VB " : electrum . util . UI_UNIT_NAME_FEERATE_SAT_PER_VB ,
" TXSIZE_VBYTES " : electrum . util . UI_UNIT_NAME_TXSIZE_VBYTES ,
" MEMPOOL_MB " : electrum . util . UI_UNIT_NAME_MEMPOOL_MB ,
} )
2022-02-08 17:02:51 +01:00
2024-04-18 17:01:08 +00:00
self . plugins . load_internal_plugin ( ' trustedcoin ' )
2022-09-22 12:43:51 +02:00
2022-02-08 17:02:51 +01:00
qInstallMessageHandler ( self . message_handler )
# get notified whether root QML document loads or not
self . engine . objectCreated . connect ( self . objectCreated )
# slot is called after loading root QML. If object is None, it has failed.
@pyqtSlot ( ' QObject* ' , ' QUrl ' )
def objectCreated ( self , object , url ) :
if object is None :
self . _valid = False
self . engine . objectCreated . disconnect ( self . objectCreated )
2023-02-27 12:20:51 +01:00
self . appController . startupFinished ( )
2022-02-08 17:02:51 +01:00
def message_handler ( self , line , funct , file ) :
# filter out common harmless messages
2022-03-18 15:00:29 +01:00
if re . search ( ' file:///.*TypeError: Cannot read property.*null$ ' , file ) :
2022-02-08 17:02:51 +01:00
return
self . logger . warning ( file )
2023-01-10 17:17:19 +01:00
2023-08-14 16:24:48 +02:00
2023-01-10 17:17:19 +01:00
class Exception_Hook ( QObject , Logger ) :
_report_exception = pyqtSignal ( object , object , object , object )
_INSTANCE = None # type: Optional[Exception_Hook] # singleton
def __init__ ( self , * , config : ' SimpleConfig ' , slot ) :
QObject . __init__ ( self )
Logger . __init__ ( self )
assert self . _INSTANCE is None , " Exception_Hook is supposed to be a singleton "
self . config = config
self . wallet_types_seen = set ( ) # type: Set[str]
sys . excepthook = self . handler
threading . excepthook = self . handler
self . _report_exception . connect ( slot )
EarlyExceptionsQueue . set_hook_as_ready ( )
@classmethod
def maybe_setup ( cls , * , config : ' SimpleConfig ' , wallet : ' Abstract_Wallet ' = None , slot = None ) - > None :
2023-05-24 17:41:44 +00:00
if not config . SHOW_CRASH_REPORTER :
2023-01-10 17:17:19 +01:00
EarlyExceptionsQueue . set_hook_as_ready ( ) # flush already queued exceptions
return
if not cls . _INSTANCE :
cls . _INSTANCE = Exception_Hook ( config = config , slot = slot )
if wallet :
cls . _INSTANCE . wallet_types_seen . add ( wallet . wallet_type )
def handler ( self , * exc_info ) :
self . logger . error ( ' exception caught by crash reporter ' , exc_info = exc_info )
self . _report_exception . emit ( self . config , * exc_info )