URI: 
       tkivy: request PIN code on startup - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit b75d82491bfe939af14e1436708a7be06751ed16
   DIR parent 95780a39a3a3c87099e9a6eab407e86392a5dce2
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Thu, 22 Mar 2018 16:39:01 +0100
       
       kivy: request PIN code on startup
       
       Diffstat:
         M gui/kivy/main.kv                    |       3 ++-
         M gui/kivy/main_window.py             |      69 ++++++++++++++-----------------
         M gui/kivy/uix/dialogs/installwizard… |      28 +++++++++-------------------
         M gui/kivy/uix/dialogs/password_dial… |      95 ++++++++++++++++++++++---------
         M gui/kivy/uix/dialogs/settings.py    |       3 +--
       
       5 files changed, 112 insertions(+), 86 deletions(-)
       ---
   DIR diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv
       t@@ -285,7 +285,8 @@
        
        <KButton@Button>:
            size_hint: 1, None
       -    height: '48dp'
       +    height: '60dp'
       +    font_size: '30dp'
            on_release:
                self.parent.update_amount(self.text)
        
   DIR diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py
       t@@ -244,6 +244,7 @@ class ElectrumWindow(App):
                self.tabs = None
                self.is_exit = False
                self.wallet = None
       +        self.pause_time = 0
        
                App.__init__(self)#, **kwargs)
        
       t@@ -445,7 +446,6 @@ class ElectrumWindow(App):
                #win.softinput_mode = 'below_target'
                self.on_size(win, win.size)
                self.init_ui()
       -        self.load_wallet_by_name(self.electrum_config.get_wallet_path())
                # init plugins
                run_hook('init_kivy', self)
                # fiat currency
       t@@ -467,6 +467,8 @@ class ElectrumWindow(App):
                    self.network.register_callback(self.on_fee, ['fee'])
                    self.network.register_callback(self.on_quotes, ['on_quotes'])
                    self.network.register_callback(self.on_history, ['on_history'])
       +        # load wallet
       +        self.load_wallet_by_name(self.electrum_config.get_wallet_path())
                # URI passed in config
                uri = self.electrum_config.get('url')
                if uri:
       t@@ -484,17 +486,18 @@ class ElectrumWindow(App):
                    wallet.start_threads(self.daemon.network)
                    self.daemon.add_wallet(wallet)
                    self.load_wallet(wallet)
       -        self.on_resume()
        
            def load_wallet_by_name(self, path):
                if not path:
                    return
       +        if self.wallet and self.wallet.storage.path == path:
       +            return
                wallet = self.daemon.load_wallet(path, None)
                if wallet:
       -            if wallet != self.wallet:
       -                self.stop_wallet()
       +            if wallet.has_password():
       +                self.password_dialog(wallet, _('Enter PIN code'), lambda x: self.load_wallet(wallet), self.stop)
       +            else:
                        self.load_wallet(wallet)
       -                self.on_resume()
                else:
                    Logger.debug('Electrum: Wallet not found. Launching install wizard')
                    storage = WalletStorage(path)
       t@@ -504,6 +507,7 @@ class ElectrumWindow(App):
                    wizard.run(action)
        
            def on_stop(self):
       +        Logger.info('on_stop')
                self.stop_wallet()
        
            def stop_wallet(self):
       t@@ -617,6 +621,8 @@ class ElectrumWindow(App):
        
            @profiler
            def load_wallet(self, wallet):
       +        if self.wallet:
       +            self.stop_wallet()
                self.wallet = wallet
                self.update_wallet()
                # Once GUI has been initialized check if we want to announce something
       t@@ -625,6 +631,7 @@ class ElectrumWindow(App):
                    self.receive_screen.clear()
                self.update_tabs()
                run_hook('load_wallet', wallet, self)
       +        print('load wallet done', self.wallet)
        
            def update_status(self, *dt):
                self.num_blocks = self.network.get_local_height()
       t@@ -684,12 +691,16 @@ class ElectrumWindow(App):
                    Logger.Error('Notification: needs plyer; `sudo pip install plyer`')
        
            def on_pause(self):
       +        self.pause_time = time.time()
                # pause nfc
                if self.nfcscanner:
                    self.nfcscanner.nfc_disable()
                return True
        
            def on_resume(self):
       +        now = time.time()
       +        if self.wallet.has_password and now - self.pause_time > 60:
       +            self.password_dialog(self.wallet, _('Enter PIN'), None, self.stop)
                if self.nfcscanner:
                    self.nfcscanner.nfc_enable()
        
       t@@ -875,7 +886,8 @@ class ElectrumWindow(App):
        
            def protected(self, msg, f, args):
                if self.wallet.has_password():
       -            self.password_dialog(msg, f, args)
       +            on_success = lambda pw: f(*(args + (pw,)))
       +            self.password_dialog(self.wallet, msg, on_success, lambda: None)
                else:
                    f(*(args + (None,)))
        
       t@@ -887,7 +899,7 @@ class ElectrumWindow(App):
        
            def _delete_wallet(self, b):
                if b:
       -            basename = os.path.basename(self.wallet.storage.path)
       +            basename = self.wallet.basename()
                    self.protected(_("Enter your PIN code to confirm deletion of {}").format(basename), self.__delete_wallet, ())
        
            def __delete_wallet(self, pw):
       t@@ -925,40 +937,23 @@ class ElectrumWindow(App):
                if passphrase:
                    label.text += '\n\n' + _('Passphrase') + ': ' + passphrase
        
       -    def change_password(self, cb):
       -        if self.wallet.has_password():
       -            self.protected(_("Changing PIN code.") + '\n' + _("Enter your current PIN:"), self._change_password, (cb,))
       -        else:
       -            self._change_password(cb, None)
       -
       -    def _change_password(self, cb, old_password):
       -        if self.wallet.has_password():
       -            if old_password is None:
       -                return
       -            try:
       -                self.wallet.check_password(old_password)
       -            except InvalidPassword:
       -                self.show_error("Invalid PIN")
       -                return
       -        self.password_dialog(_('Enter new PIN'), self._change_password2, (cb, old_password,))
       -
       -    def _change_password2(self, cb, old_password, new_password):
       -        self.password_dialog(_('Confirm new PIN'), self._change_password3, (cb, old_password, new_password))
       -
       -    def _change_password3(self, cb, old_password, new_password, confirmed_password):
       -        if new_password == confirmed_password:
       -            self.wallet.update_password(old_password, new_password)
       -            cb()
       -        else:
       -            self.show_error("PIN numbers do not match")
       +    def password_dialog(self, wallet, msg, on_success, on_failure):
       +        from .uix.dialogs.password_dialog import PasswordDialog
       +        if self._password_dialog is None:
       +            self._password_dialog = PasswordDialog()
       +        self._password_dialog.init(self, wallet, msg, on_success, on_failure)
       +        self._password_dialog.open()
        
       -    def password_dialog(self, msg, f, args):
       +    def change_password(self, cb):
                from .uix.dialogs.password_dialog import PasswordDialog
       -        def callback(pw):
       -            Clock.schedule_once(lambda x: f(*(args + (pw,))), 0.1)
                if self._password_dialog is None:
                    self._password_dialog = PasswordDialog()
       -        self._password_dialog.init(msg, callback)
       +        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, self.wallet, message, on_success, on_failure, is_change=1)
                self._password_dialog.open()
        
            def export_private_keys(self, pk_label, addr):
   DIR diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py
       t@@ -802,28 +802,18 @@ class InstallWizard(BaseWizard, Widget):
                app = App.get_running_app()
                Clock.schedule_once(lambda dt: app.show_error(msg))
        
       -    def password_dialog(self, message, callback):
       +    def request_password(self, run_next, force_disable_encrypt_cb=False):
       +        def on_success(old_pin, pin):
       +            assert old_pin is None
       +            run_next(pin, False)
       +        def on_failure():
       +            self.show_error(_('PIN mismatch'))
       +            self.run('request_password', run_next)
                popup = PasswordDialog()
       -        popup.init(message, callback)
       +        app = App.get_running_app()
       +        popup.init(app, None, _('Choose PIN code'), on_success, on_failure, is_change=2)
                popup.open()
        
       -    def request_password(self, run_next, force_disable_encrypt_cb=False):
       -        def callback(pin):
       -            if pin:
       -                self.run('confirm_password', pin, run_next)
       -            else:
       -                run_next(None, None)
       -        self.password_dialog('Choose a PIN code', callback)
       -
       -    def confirm_password(self, pin, run_next):
       -        def callback(conf):
       -            if conf == pin:
       -                run_next(pin, False)
       -            else:
       -                self.show_error(_('PIN mismatch'))
       -                self.run('request_password', run_next)
       -        self.password_dialog('Confirm your PIN code', callback)
       -
            def action_dialog(self, action, run_next):
                f = getattr(self, action)
                f()
   DIR diff --git a/gui/kivy/uix/dialogs/password_dialog.py b/gui/kivy/uix/dialogs/password_dialog.py
       t@@ -5,35 +5,42 @@ from kivy.lang import Builder
        from decimal import Decimal
        from kivy.clock import Clock
        
       +from electrum.util import InvalidPassword
       +from electrum_gui.kivy.i18n import _
       +
        Builder.load_string('''
        
        <PasswordDialog@Popup>
            id: popup
       -    title: _('PIN Code')
       +    title: 'Electrum'
            message: ''
       -    size_hint: 0.9, 0.9
            BoxLayout:
       +        size_hint: 1, 1
                orientation: 'vertical'
                Widget:
       -            size_hint: 1, 1
       +            size_hint: 1, 0.05
                Label:
       +            font_size: '20dp'
                    text: root.message
                    text_size: self.width, None
                    size: self.texture_size
                Widget:
       -            size_hint: 1, 1
       +            size_hint: 1, 0.05
                Label:
                    id: a
       -            text: ' * '*len(kb.password) + ' o '*(6-len(kb.password))
       +            font_size: '50dp'
       +            text: '*'*len(kb.password) + '-'*(6-len(kb.password))
       +            size: self.texture_size
                Widget:
       -            size_hint: 1, 1
       +            size_hint: 1, 0.05
                GridLayout:
                    id: kb
       +            size_hint: 1, None
       +            height: self.minimum_height
                    update_amount: popup.update_password
                    password: ''
                    on_password: popup.on_password(self.password)
       -            size_hint: 1, None
       -            height: '200dp'
       +            spacing: '2dp'
                    cols: 3
                    KButton:
                        text: '1'
       t@@ -59,30 +66,44 @@ Builder.load_string('''
                        text: '0'
                    KButton:
                        text: '<'
       -        BoxLayout:
       -            size_hint: 1, None
       -            height: '48dp'
       -            Widget:
       -                size_hint: 0.5, None
       -            Button:
       -                size_hint: 0.5, None
       -                height: '48dp'
       -                text: _('Cancel')
       -                on_release:
       -                    popup.dismiss()
       -                    popup.callback(None)
        ''')
        
        
        class PasswordDialog(Factory.Popup):
        
       -    #def __init__(self, message, callback):
       -    #    Factory.Popup.__init__(self)
       -
       -    def init(self, message, callback):
       +    def init(self, app, wallet, message, on_success, on_failure, is_change=0):
       +        self.app = app
       +        self.wallet = wallet
                self.message = message
       -        self.callback = callback
       +        self.on_success = on_success
       +        self.on_failure = on_failure
                self.ids.kb.password = ''
       +        self.success = False
       +        self.is_change = is_change
       +        self.pw = None
       +        self.new_password = None
       +        self.title = 'Electrum' + ('  -  ' + self.wallet.basename() if self.wallet else '')
       +
       +    def check_password(self, password):
       +        if self.is_change > 1:
       +            return True
       +        try:
       +            self.wallet.check_password(password)
       +            return True
       +        except InvalidPassword as e:
       +            return False
       +
       +    def on_dismiss(self):
       +        if not self.success:
       +            if self.on_failure:
       +                self.on_failure()
       +            else:
       +                # keep dialog open
       +                return True
       +        else:
       +            if self.on_success:
       +                args = (self.pw, self.new_password) if self.is_change else (self.pw,)
       +                Clock.schedule_once(lambda dt: self.on_success(*args), 0.1)
        
            def update_password(self, c):
                kb = self.ids.kb
       t@@ -97,5 +118,25 @@ class PasswordDialog(Factory.Popup):
        
            def on_password(self, pw):
                if len(pw) == 6:
       -            self.dismiss()
       -            Clock.schedule_once(lambda dt: self.callback(pw), 0.1)
       +            if self.check_password(pw):
       +                if self.is_change == 0:
       +                    self.success = True
       +                    self.pw = pw
       +                    self.message = _('Please wait...')
       +                    self.dismiss()
       +                elif self.is_change == 1:
       +                    self.pw = pw
       +                    self.message = _('Enter new PIN')
       +                    self.ids.kb.password = ''
       +                    self.is_change = 2
       +                elif self.is_change == 2:
       +                    self.new_password = pw
       +                    self.message = _('Confirm new PIN')
       +                    self.ids.kb.password = ''
       +                    self.is_change = 3
       +                elif self.is_change == 3:
       +                    self.success = pw == self.new_password
       +                    self.dismiss()
       +            else:
       +                self.app.show_error(_('Wrong PIN'))
       +                self.ids.kb.password = ''
   DIR diff --git a/gui/kivy/uix/dialogs/settings.py b/gui/kivy/uix/dialogs/settings.py
       t@@ -36,9 +36,8 @@ Builder.load_string('''
                            action: partial(root.language_dialog, self)
                        CardSeparator
                        SettingsItem:
       -                    status: '' if root.disable_pin else ('ON' if root.use_encryption else 'OFF')
                            disabled: root.disable_pin
       -                    title: _('PIN code') + ': ' + self.status
       +                    title: _('PIN code')
                            description: _("Change your PIN code.")
                            action: partial(root.change_password, self)
                        CardSeparator