@ -7,7 +7,7 @@ import traceback
from decimal import Decimal
import threading
import asyncio
from typing import TYPE_CHECKING , Optional , Union , Callable
from typing import TYPE_CHECKING , Optional , Union , Callable , Sequence
from electrum . storage import WalletStorage , StorageReadWriteError
from electrum . wallet_db import WalletDB
@ -31,6 +31,7 @@ from kivy.clock import Clock
from kivy . factory import Factory
from kivy . metrics import inch
from kivy . lang import Builder
from . uix . dialogs . password_dialog import PasswordDialog
## lazy imports for factory so that widgets can be used in kv
#Factory.register('InstallWizard', module='electrum.gui.kivy.uix.dialogs.installwizard')
@ -163,6 +164,10 @@ class ElectrumWindow(App):
def on_use_rbf ( self , instance , x ) :
self . electrum_config . set_key ( ' use_rbf ' , self . use_rbf , True )
android_backups = BooleanProperty ( False )
def on_android_backups ( self , instance , x ) :
self . electrum_config . set_key ( ' android_backups ' , self . android_backups , True )
use_change = BooleanProperty ( False )
def on_use_change ( self , instance , x ) :
if self . wallet :
@ -326,6 +331,7 @@ class ElectrumWindow(App):
self . wallet = None # type: Optional[Abstract_Wallet]
self . pause_time = 0
self . asyncio_loop = asyncio . get_event_loop ( )
self . password = None
App . __init__ ( self ) #, **kwargs)
@ -619,9 +625,15 @@ class ElectrumWindow(App):
return
wallet = self . daemon . load_wallet ( path , None )
if wallet :
if platform == ' android ' and wallet . has_password ( ) :
self . password_dialog ( wallet = wallet , msg = _ ( ' Enter PIN code ' ) ,
on_success = lambda x : self . load_wallet ( wallet ) , on_failure = self . stop )
if wallet . has_password ( ) :
def on_success ( x ) :
# save pin_code so that we can create backups
self . password = x
self . load_wallet ( wallet )
self . password_dialog (
check_password = wallet . check_password ,
on_success = on_success ,
on_failure = self . stop )
else :
self . load_wallet ( wallet )
else :
@ -637,10 +649,13 @@ class ElectrumWindow(App):
if not storage . is_encrypted_with_user_pw ( ) :
raise Exception ( " Kivy GUI does not support this type of encrypted wallet files. " )
def on_password ( pw ) :
self . password = pw
storage . decrypt ( pw )
self . _on_decrypted_storage ( storage )
self . password_dialog ( wallet = storage , msg = _ ( ' Enter PIN code ' ) ,
on_success = on_password , on_failure = self . stop )
self . password_dialog (
check_password = storage . check_password ,
on_success = on_password ,
on_failure = self . stop )
return
self . _on_decrypted_storage ( storage )
if not ask_if_wizard :
@ -934,7 +949,7 @@ class ElectrumWindow(App):
def on_resume ( self ) :
now = time . time ( )
if self . wallet and self . wallet . has_password ( ) and now - self . pause_time > 60 :
self . password_dialog ( wallet = self . wallet , msg = _ ( ' Enter PIN ' ) , on_success = None , on_failure = self . stop )
self . password_dialog ( check_password = self . check_pin_code , on_success = None , on_failure = self . stop , is_password = False )
if self . nfcscanner :
self . nfcscanner . nfc_enable ( )
@ -1096,12 +1111,12 @@ class ElectrumWindow(App):
def on_fee ( self , event , * arg ) :
self . fee_status = self . electrum_config . get_fee_status ( )
def protected ( self , msg , f , args ) :
if self . wallet . has_password ( ) :
on_success = lambda pw : f ( * ( args + ( p w, ) ) )
self . password_dialog ( wallet = self . wallet , msg = msg , on_success = on_success , on_failure = lambda : None )
def protected ( self , f , args ) :
if self . electrum_config . get ( ' pin_code ' ) :
on_success = lambda pw : f ( * ( args + ( self . pass word , ) ) )
self . password_dialog ( check_password = self . check_pin_code , on_success = on_success , on_failure = lambda : None , is_password = Fals e )
else :
f ( * ( args + ( None , ) ) )
f ( * ( args + ( self . password , ) ) )
def toggle_lightning ( self ) :
if self . wallet . has_lightning ( ) :
@ -1161,44 +1176,88 @@ class ElectrumWindow(App):
self . load_wallet_by_name ( new_path )
def show_seed ( self , label ) :
self . protected ( _ ( " Enter your PIN code in order to decrypt your seed " ) , self . _show_seed , ( label , ) )
self . protected ( self . _show_seed , ( label , ) )
def _show_seed ( self , label , password ) :
if self . wallet . has_password ( ) and password is None :
return
keystore = self . wallet . keystore
try :
seed = keystore . get_seed ( password )
passphrase = keystore . get_passphrase ( password )
except :
self . show_error ( " Invalid PIN " )
return
seed = keystore . get_seed ( password )
passphrase = keystore . get_passphrase ( password )
label . data = seed
if passphrase :
label . data + = ' \n \n ' + _ ( ' Passphrase ' ) + ' : ' + passphrase
def password_dialog ( self , * , wallet : Union [ Abstract_Wallet , WalletStorage ] ,
msg : str , on_success : Callable = None , on_failure : Callable = None ) :
from . uix . dialogs . password_dialog import PasswordDialog
def has_pin_code ( self ) :
return bool ( self . electrum_config . get ( ' pin_code ' ) )
def check_pin_code ( self , pin ) :
if pin != self . electrum_config . get ( ' pin_code ' ) :
raise InvalidPassword
def password_dialog ( self , * , check_password : Callable = None ,
on_success : Callable = None , on_failure : Callable = None ,
is_password = True ) :
if self . _password_dialog is None :
self . _password_dialog = PasswordDialog ( )
self . _password_dialog . init ( self , wallet = wallet , msg = msg ,
on_success = on_success , on_failure = on_failure )
self . _password_dialog . init (
self , check_password = check_password ,
on_success = on_success , on_failure = on_failure ,
is_password = is_password )
self . _password_dialog . open ( )
def change_password ( self , cb ) :
from . uix . dialogs . password_dialog import PasswordDialog
if self . _password_dialog is None :
self . _password_dialog = PasswordDialog ( )
message = _ ( " Changing PIN code. " ) + ' \n ' + _ ( " Enter your current PIN: " )
def on_success ( old_password , new_password ) :
self . wallet . update_password ( old_password , new_password )
self . show_info ( _ ( " Your PIN code was updated " ) )
on_failure = lambda : self . show_error ( _ ( " PIN codes do not match " ) )
self . _password_dialog . init ( self , wallet = self . wallet , msg = message ,
on_success = on_success , on_failure = on_failure , is_change = 1 )
self . password = new_password
self . show_info ( _ ( " Your password was updated " ) )
on_failure = lambda : self . show_error ( _ ( " Password not updated " ) )
self . _password_dialog . init (
self , check_password = self . wallet . check_password ,
on_success = on_success , on_failure = on_failure ,
is_change = True , is_password = True ,
has_password = self . wallet . has_password ( ) )
self . _password_dialog . open ( )
def change_pin_code ( self , cb ) :
if self . _password_dialog is None :
self . _password_dialog = PasswordDialog ( )
def on_success ( old_password , new_password ) :
self . electrum_config . set_key ( ' pin_code ' , new_password )
cb ( )
self . show_info ( _ ( " PIN updated " ) if new_password else _ ( ' PIN disabled ' ) )
on_failure = lambda : self . show_error ( _ ( " PIN not updated " ) )
self . _password_dialog . init (
self , check_password = self . check_pin_code ,
on_success = on_success , on_failure = on_failure ,
is_change = True , is_password = False ,
has_password = self . has_pin_code ( ) )
self . _password_dialog . open ( )
def save_backup ( self ) :
if platform != ' android ' :
self . _save_backup ( )
return
from android . permissions import request_permissions , Permission
def cb ( permissions , grant_results : Sequence [ bool ] ) :
if not grant_results or not grant_results [ 0 ] :
self . show_error ( _ ( " Cannot save backup without STORAGE permission " ) )
return
# note: Clock.schedule_once is a hack so that we get called on a non-daemon thread
# (needed for WalletDB.write)
Clock . schedule_once ( lambda dt : self . _save_backup ( ) )
request_permissions ( [ Permission . WRITE_EXTERNAL_STORAGE ] , cb )
def _save_backup ( self ) :
new_path = self . wallet . save_backup ( )
if new_path :
self . show_info ( _ ( " Backup saved: " ) + f " \n { new_path } " )
else :
self . show_error ( _ ( " Backup NOT saved. Backup directory not configured. " ) )
def export_private_keys ( self , pk_label , addr ) :
if self . wallet . is_watching_only ( ) :
self . show_info ( _ ( ' This is a watching-only wallet. It does not contain private keys. ' ) )