def __init__(self, parent: QWidget, message: str, task, on_success=None, on_error=None): assert parent if isinstance(parent, MessageBoxMixin): parent = parent.top_level_window() WindowModalDialog.__init__(self, parent, _("Please wait")) self.message_label = QLabel(message) vbox = QVBoxLayout(self) vbox.addWidget(self.message_label) self.accepted.connect(self.on_accepted) self.show() self.thread = TaskThread(self) self.thread.finished.connect(self.deleteLater) # see #3956 self.thread.add(task, on_success, self.accept, on_error)
def add_copy_menu(self, menu, idx): cc = menu.addMenu(_("Copy")) for column in HistoryColumns: if column == HistoryColumns.TX_GROUP: continue if self.isColumnHidden(column): continue column_title = self.hm.headerData(column, Qt.Horizontal, Qt.DisplayRole) idx2 = idx.sibling(idx.row(), column) tx_item = self.hm.data(idx2, Qt.DisplayRole) if not tx_item: continue column_data = (tx_item.value() or '').strip() cc.addAction(column_title, lambda text=column_data, title=column_title: self. place_text_on_clipboard(text, title=title)) return cc
def on_send(self): password = self.pw.text() or None if self.password_required: if password is None: self.main_window.show_error(_("Password required"), parent=self) return try: psman = self.wallet.psman if psman.is_hw_ks and psman.is_ps_ks_encrypted(): psman.ps_keystore.check_password(password) else: self.wallet.check_password(password) except Exception as e: self.main_window.show_error(str(e), parent=self) return self.is_send = True self.accept()
def setup_device(self, device_info, wizard, purpose): device_id = device_info.device.id_ client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) if not client.is_uptodate(): msg = (_('Outdated {} firmware for device labelled {}. Please ' 'download the updated firmware from {}').format( self.device, client.label(), self.firmware_URL)) raise OutdatedHwFirmwareException(msg) if not device_info.initialized: self.initialize_device(device_id, wizard, client.handler) is_creating_wallet = purpose == HWD_SETUP_NEW_WALLET wizard.run_task_without_blocking_gui(task=lambda: client.get_xpub( 'm', 'standard', creating=is_creating_wallet)) client.used() return client
def __init__(self, config: 'SimpleConfig', app: QApplication, plugins: 'Plugins', *, gui_object: 'ElectrumGui'): QDialog.__init__(self, None) BaseWizard.__init__(self, config, plugins) self.setWindowTitle('Dash Electrum - ' + _('Install Wizard')) self.app = app self.config = config self.gui_thread = gui_object.gui_thread self.setMinimumSize(600, 400) self.accept_signal.connect(self.accept) self.title = QLabel() self.main_widget = QWidget() self.back_button = QPushButton(_("Back"), self) self.back_button.setText( _('Back') if self.can_go_back() else _('Cancel')) self.next_button = QPushButton(_("Next"), self) self.next_button.setDefault(True) self.logo = QLabel() self.please_wait = QLabel(_("Please wait...")) self.please_wait.setAlignment(Qt.AlignCenter) self.icon_filename = None self.loop = QEventLoop() self.rejected.connect(lambda: self.loop.exit(0)) self.back_button.clicked.connect(lambda: self.loop.exit(1)) self.next_button.clicked.connect(lambda: self.loop.exit(2)) outer_vbox = QVBoxLayout(self) inner_vbox = QVBoxLayout() inner_vbox.addWidget(self.title) inner_vbox.addWidget(self.main_widget) inner_vbox.addStretch(1) inner_vbox.addWidget(self.please_wait) inner_vbox.addStretch(1) scroll_widget = QWidget() scroll_widget.setLayout(inner_vbox) scroll = QScrollArea() scroll.setWidget(scroll_widget) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setWidgetResizable(True) icon_vbox = QVBoxLayout() icon_vbox.addWidget(self.logo) icon_vbox.addStretch(1) hbox = QHBoxLayout() hbox.addLayout(icon_vbox) hbox.addSpacing(5) hbox.addWidget(scroll) hbox.setStretchFactor(scroll, 1) outer_vbox.addLayout(hbox) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) self.set_icon('electrum-dash.png') self.show() self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame.
def calibration_dialog(self, window): d = WindowModalDialog(window, _("Revealer - Printer calibration settings")) d.setMinimumSize(100, 200) vbox = QVBoxLayout(d) vbox.addWidget( QLabel(''.join([ "<br/>", _("If you have an old printer, or want optimal precision"), "<br/>", _("print the calibration pdf and follow the instructions "), "<br/>", "<br/>", ]))) self.calibration_h = self.config.get('calibration_h') self.calibration_v = self.config.get('calibration_v') cprint = QPushButton(_("Open calibration pdf")) cprint.clicked.connect(self.calibration) vbox.addWidget(cprint) vbox.addWidget(QLabel(_('Calibration values:'))) grid = QGridLayout() vbox.addLayout(grid) grid.addWidget(QLabel(_('Right side')), 0, 0) horizontal = QLineEdit() horizontal.setText(str(self.calibration_h)) grid.addWidget(horizontal, 0, 1) grid.addWidget(QLabel(_('Bottom')), 1, 0) vertical = QLineEdit() vertical.setText(str(self.calibration_v)) grid.addWidget(vertical, 1, 1) vbox.addStretch() vbox.addSpacing(13) vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) if not d.exec_(): return self.calibration_h = int(Decimal(horizontal.text())) self.config.set_key('calibration_h', self.calibration_h) self.calibration_v = int(Decimal(vertical.text())) self.config.set_key('calibration_v', self.calibration_v)
def receive_menu(self, menu, addrs, wallet: Abstract_Wallet): if type(wallet) is not Standard_Wallet: return keystore = wallet.get_keystore() if type(keystore) is not self.keystore_class: return if not self.is_mobile_paired(): return if len(addrs) == 1: addr = addrs[0] if wallet.get_txin_type(addr) != 'p2pkh': return def show_address(): keystore.thread.add(partial(self.show_address, wallet, addr, keystore)) menu.addAction(_("Show on {}").format(self.device), show_address)
def create_menu(self, position): wallet = self.parent.wallet items = self.selected_in_column(0) if len(items) > 1: keys = [item.data(ROLE_REQUEST_ID) for item in items] invoices = [wallet.invoices.get(key) for key in keys] can_batch_pay = all([ i.type == PR_TYPE_ONCHAIN and wallet.get_invoice_status(i) == PR_UNPAID for i in invoices ]) menu = QMenu(self) if can_batch_pay: menu.addAction( _("Batch pay invoices") + "...", lambda: self.parent.pay_multiple_invoices(invoices)) menu.addAction(_("Delete invoices"), lambda: self.parent.delete_invoices(keys)) menu.exec_(self.viewport().mapToGlobal(position)) return idx = self.indexAt(position) item = self.item_from_index(idx) item_col0 = self.item_from_index( idx.sibling(idx.row(), self.Columns.DATE)) if not item or not item_col0: return key = item_col0.data(ROLE_REQUEST_ID) invoice = self.parent.wallet.get_invoice(key) menu = QMenu(self) self.add_copy_menu(menu, idx) if len(invoice.outputs) == 1: menu.addAction( _("Copy Address"), lambda: self.parent.do_copy(invoice.get_address(), title='Dash Address')) menu.addAction(_("Details"), lambda: self.parent.show_onchain_invoice(invoice)) status = wallet.get_invoice_status(invoice) if status == PR_UNPAID: menu.addAction( _("Pay") + "...", lambda: self.parent.do_pay_invoice(invoice)) if status == PR_FAILED: menu.addAction(_("Retry"), lambda: self.parent.do_pay_invoice(invoice)) menu.addAction(_("Delete"), lambda: self.parent.delete_invoices([key])) menu.exec_(self.viewport().mapToGlobal(position))
def on_task_thread_error(self: Union['QtPluginBase', HW_PluginBase], window: ElectrumWindow, keystore: 'Hardware_KeyStore', exc_info): e = exc_info[1] if isinstance(e, OutdatedHwFirmwareException): if window.question(e.text_ignore_old_fw_and_continue(), title=_("Outdated device firmware")): self.set_ignore_outdated_fw() # will need to re-pair devmgr = self.device_manager() def re_pair_device(): device_id = self.choose_device(window, keystore) devmgr.unpair_id(device_id) self.get_client(keystore) keystore.thread.add(re_pair_device) return else: window.on_error(exc_info)
def run_upgrades(self, storage: WalletStorage, db: 'WalletDB') -> None: path = storage.path if db.requires_split(): self.hide() msg = _( "The wallet '{}' contains multiple accounts, which are no longer supported since Firo Electrum 2.7.\n\n" "Do you want to split your wallet into multiple files?" ).format(path) if not self.question(msg): return file_list = db.split_accounts(path) msg = _('Your accounts have been moved to') + ':\n' + '\n'.join( file_list) + '\n\n' + _( 'Do you want to delete the old file') + ':\n' + path if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) # raise now, to avoid having the old storage opened raise UserCancelled() action = db.get_action() if action and db.requires_upgrade(): raise WalletFileException( 'Incomplete wallet files cannot be upgraded.') if action: self.hide() msg = _("The file '{}' contains an incompletely created wallet.\n" "Do you want to complete its creation now?").format(path) if not self.question(msg): if self.question( _("Do you want to delete '{}'?").format(path)): os.remove(path) self.show_warning(_('The file was removed')) return self.show() self.data = json.loads(storage.read()) self.run(action) for k, v in self.data.items(): db.put(k, v) db.write(storage) return if db.requires_upgrade(): self.upgrade_db(storage, db)
def file_input(self): fileName = getOpenFileName( parent=self, title='select file', config=self.config, ) if not fileName: return try: try: with open(fileName, "r") as f: data = f.read() except UnicodeError as e: with open(fileName, "rb") as f: data = f.read() data = data.hex() except BaseException as e: self.show_error(_('Error opening file') + ':\n' + repr(e)) else: self.setText(data)
def select_date(self, button): d = WindowModalDialog(self, _("Select date")) d.setMinimumSize(600, 150) d.date = None vbox = QVBoxLayout() def on_date(date): d.date = date cal = QCalendarWidget() cal.setGridVisible(True) cal.clicked[QDate].connect(on_date) vbox.addWidget(cal) vbox.addLayout(Buttons(OkButton(d), CancelButton(d))) d.setLayout(vbox) if d.exec_(): if d.date is None: return None date = d.date.toPyDate() button.setText(self.format_date(date)) return datetime.datetime(date.year, date.month, date.day)
def create_wallet_menu(self, position): a = self.w_cur_alias s = self.w_cur_state i = self.w_cur_idx if not i: return mn = self.manager.mns.get(a) owned = mn.is_owned operated = mn.is_operated protx_hash = mn.protx_hash removed = (s == WalletMNsModel.STATE_REMOVED) menu = QMenu() menu.addAction(_('Remove'), self.on_del_masternode) if not protx_hash: menu.addAction(_('Update Params'), self.on_update_params) if removed and owned: menu.addAction(_('Change Collateral'), self.on_update_collateral) if owned and not protx_hash: menu.addAction(_('Register Masternode'), self.on_make_pro_reg_tx) if operated and protx_hash and not removed: menu.addAction(_('Update Service'), self.on_make_pro_up_srv_tx) if owned and protx_hash and not removed: menu.addAction(_('Update Registrar'), self.on_make_pro_up_reg_tx) menu.addSeparator() menu.addAction(_('Rename Alias'), lambda: self.w_view.edit(i)) menu.addSeparator() menu.addAction(_('Details'), lambda: self.show_alias_info(a)) menu.exec_(self.w_view.viewport().mapToGlobal(position))
def address_field(addresses): hbox = QHBoxLayout() address_e = QLineEdit() if addresses and len(addresses) > 0: address_e.setText(addresses[0]) else: addresses = [] def func(): try: i = addresses.index(str(address_e.text())) + 1 i = i % len(addresses) address_e.setText(addresses[i]) except ValueError: # the user might have changed address_e to an # address not in the wallet (or to something that isn't an address) if addresses and len(addresses) > 0: address_e.setText(addresses[0]) button = QPushButton(_('Address')) button.clicked.connect(func) hbox.addWidget(button) hbox.addWidget(address_e) return hbox, address_e
def do_send(self, tx: Union[Transaction, PartialTransaction]): def on_success(result): window.show_message( _("Your transaction was sent to the cosigning pool.") + '\n' + _("Open your cosigner wallet to retrieve it.")) def on_failure(exc_info): e = exc_info[1] try: self.logger.error("on_failure", exc_info=exc_info) except OSError: pass window.show_error( _("Failed to send transaction to cosigning pool") + ':\n' + repr(e)) buffer = [] some_window = None # construct messages for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue some_window = window raw_tx_bytes = tx.serialize_as_bytes() public_key = ecc.ECPubkey(K) message = public_key.encrypt_message(raw_tx_bytes).decode('ascii') buffer.append((_hash, message)) if not buffer: return # send messages # note: we send all messages sequentially on the same thread def send_messages_task(): for _hash, message in buffer: server.put(_hash, message) msg = _('Sending transaction to cosigning pool...') WaitingDialog(some_window, msg, send_messages_task, on_success, on_failure)
def run_task_without_blocking_gui(self, task, *, msg=None): assert self.gui_thread == threading.current_thread( ), 'must be called from GUI thread' if msg is None: msg = _("Please wait...") exc = None # type: Optional[Exception] res = None def task_wrapper(): nonlocal exc nonlocal res try: res = task() except Exception as e: exc = e self.waiting_dialog(task_wrapper, msg=msg) if exc is None: return res else: raise exc
def __init__(self, msg, wallet=None): self.wallet = wallet vbox = QVBoxLayout() label = QLabel(msg + "\n") label.setWordWrap(True) grid = QGridLayout() grid.setSpacing(8) grid.setColumnMinimumWidth(0, 150) grid.setColumnMinimumWidth(1, 100) grid.setColumnStretch(1, 1) logo_grid = QGridLayout() logo_grid.setSpacing(8) logo_grid.setColumnMinimumWidth(0, 70) logo_grid.setColumnStretch(1, 1) logo = QLabel() logo.setAlignment(Qt.AlignCenter) logo_grid.addWidget(logo, 0, 0) logo_grid.addWidget(label, 0, 1, 1, 2) vbox.addLayout(logo_grid) if wallet and wallet.has_storage_encryption(): lockfile = "lock.png" else: lockfile = "unlock.png" logo.setPixmap( QPixmap(icon_path(lockfile)).scaledToWidth( 36, mode=Qt.SmoothTransformation)) vbox.addLayout(grid) self.encrypt_cb = QCheckBox(_('Encrypt wallet file')) grid.addWidget(self.encrypt_cb, 1, 0, 1, 2) self.vbox = vbox
def _on_camera_status_changed(self, status: QCamera.Status): if self._ok_done: # camera/scan is quitting, abort. return self.logger.info('camera status changed to {}'.format( self._get_camera_status_name(status))) if status == QCamera.LoadedStatus: # Determine the optimal resolution and compute the crop rect camera_resolutions = self.camera.supportedViewfinderResolutions() try: resolution, was_ideal = self._get_resolution( camera_resolutions, self.SCAN_SIZE) except RuntimeError as e: self._error_message = str(e) self.reject() return self._set_resolution(resolution) # Set the camera resolution viewfinder_settings = QCameraViewfinderSettings() viewfinder_settings.setResolution(resolution) self.camera.setViewfinderSettings(viewfinder_settings) # Counter for the QR scanner frame number self.frame_id = 0 self.camera.start() self.lowres_label.setVisible( not was_ideal ) # if they have a low res camera, show the warning label. elif status == QCamera.UnloadedStatus or status == QCamera.UnavailableStatus: self._error_message = _( "Cannot start QR scanner, camera is unavailable.") self.reject() elif status == QCamera.ActiveStatus: self.open()
def on_receive(self, keyhash, message): self.logger.info(f"signal arrived for {keyhash}") for key, _hash, window in self.keys: if _hash == keyhash: break else: self.logger.info("keyhash not found") return wallet = window.wallet if isinstance(wallet.keystore, keystore.Hardware_KeyStore): window.show_warning(_('An encrypted transaction was retrieved from cosigning pool.') + '\n' + _('However, hardware wallets do not support message decryption, ' 'which makes them not compatible with the current design of cosigner pool.')) return elif wallet.has_keystore_encryption(): password = window.password_dialog(_('An encrypted transaction was retrieved from cosigning pool.') + '\n' + _('Please enter your password to decrypt it.')) if not password: return else: password = None if not window.question(_("An encrypted transaction was retrieved from cosigning pool.") + '\n' + _("Do you want to open it now?")): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: privkey = BIP32Node.from_xkey(xprv).eckey message = privkey.decrypt_message(message) except Exception as e: self.logger.exception('') window.show_error(_('Error decrypting message') + ':\n' + repr(e)) return self.listener.clear(keyhash) tx = tx_from_any(message) show_transaction(tx, parent=window, prompt_if_unsaved=True)
def _start_wizard_to_select_or_create_wallet( self, path) -> Optional[Abstract_Wallet]: wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) try: path, storage = wizard.select_storage(path, self.daemon.get_wallet) # storage is None if file does not exist if storage is None: wizard.path = path # needed by trustedcoin plugin wizard.run('new') storage, db = wizard.create_storage(path) else: db = WalletDB(storage.read(), manual_upgrades=False) if db.upgrade_done: storage.backup_old_version() wizard.run_upgrades(storage, db) if getattr(storage, 'backup_message', None): custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Information'), text=storage.backup_message) storage.backup_message = '' except (UserCancelled, GoBack): return except WalletAlreadyOpenInMemory as e: return e.wallet finally: wizard.terminate() # return if wallet creation is not complete if storage is None or db.get_action(): return wallet = Wallet(db, storage, config=self.config) wallet.start_network(self.daemon.network) self.daemon.add_wallet(wallet) return wallet
def update(self): # not calling maybe_defer_update() as it interferes with conditional-visibility self.proxy.setDynamicSortFilter( False) # temp. disable re-sorting after every change self.std_model.clear() self.update_headers(self.__class__.headers) for idx, item in enumerate(self.parent.wallet.get_invoices()): key = item.id icon_name = 'firocoin.png' if item.bip70: icon_name = 'seal.png' status = self.parent.wallet.get_invoice_status(item) status_str = item.get_status_str(status) message = item.message amount = item.get_amount_sat() timestamp = item.time or 0 date_str = format_time(timestamp) if timestamp else _('Unknown') amount_str = self.parent.format_amount(amount, whitespaces=True) labels = [date_str, message, amount_str, status_str] items = [QStandardItem(e) for e in labels] self.set_editability(items) items[self.Columns.DATE].setIcon(read_QIcon(icon_name)) items[self.Columns.STATUS].setIcon(read_QIcon( pr_icons.get(status))) items[self.Columns.DATE].setData(key, role=ROLE_REQUEST_ID) items[self.Columns.DATE].setData(item.type, role=ROLE_REQUEST_TYPE) items[self.Columns.DATE].setData(timestamp, role=ROLE_SORT_ORDER) self.std_model.insertRow(idx, items) self.filter() self.proxy.setDynamicSortFilter(True) # sort requests by date self.sortByColumn(self.Columns.DATE, Qt.DescendingOrder) # hide list if empty if self.parent.isVisible(): b = self.std_model.rowCount() > 0 self.setVisible(b) self.parent.invoices_label.setVisible(b)
def on_edit(self): s = self.get_seed() b = self.is_seed(s) if not self.is_bip39: t = seed_type(s) label = _('Seed Type') + ': ' + t if t else '' else: from electrum_firo.keystore import bip39_is_checksum_valid is_checksum, is_wordlist = bip39_is_checksum_valid(s) status = ('checksum: ' + ('ok' if is_checksum else 'failed') ) if is_wordlist else 'unknown wordlist' label = 'BIP39' + ' (%s)' % status self.seed_type_label.setText(label) if self.on_edit_cb: self.on_edit_cb(b) else: self.parent.next_button.setEnabled(b) # disable suggestions if user already typed an unknown word for word in self.get_seed().split(" ")[:-1]: if word not in self.wordlist: self.seed_e.disable_suggestions() return self.seed_e.enable_suggestions()
def update_view(self, latest_version=None): if latest_version: self.pb.hide() self.latest_version_label.setText( _("Latest version: {}".format(latest_version))) if self.is_newer(latest_version): self.heading_label.setText( '<h2>' + _("There is a new update available") + '</h2>') url = "<a href='{u}'>{u}</a>".format( u=UpdateCheck.download_url) self.detail_label.setText( _("You can download the new version from {}.").format(url)) else: self.heading_label.setText('<h2>' + _("Already up to date") + '</h2>') self.detail_label.setText( _("You are already on the latest version of Firo Electrum." )) else: self.heading_label.setText('<h2>' + _("Checking for updates...") + '</h2>') self.detail_label.setText( _("Please wait while Firo Electrum checks for available updates." ))
def initialize_device(self, device_id, wizard, handler): # Initialization method msg = _("Choose how you want to initialize your {}.\n\n" "The first two methods are secure as no secret information " "is entered into your computer.\n\n" "For the last two methods you input secrets on your keyboard " "and upload them to your {}, and so you should " "only do those on a computer you know to be trustworthy " "and free of malware.").format(self.device, self.device) choices = [ # Must be short as QT doesn't word-wrap radio button text (TIM_NEW, _("Let the device generate a completely new seed randomly")), (TIM_RECOVER, _("Recover from a seed you have previously written down")), (TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")), (TIM_PRIVKEY, _("Upload a master private key")) ] def f(method): import threading settings = self.request_trezor_init_settings( wizard, method, self.device) t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler)) t.setDaemon(True) t.start() exit_code = wizard.loop.exec_() if exit_code != 0: # this method (initialize_device) was called with the expectation # of leaving the device in an initialized state when finishing. # signal that this is not the case: raise UserCancelled() wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f)
def change_ps_ks_password_dialog(self): psman = self.psman d = PSKsPasswordDlg(self, self.wallet) ok, old_password, new_password = d.run() if not ok: return try: psman.update_ps_ks_password(old_password, new_password) except InvalidPassword as e: self.show_error(str(e)) return except BaseException: self.logger.exception('Failed to update PS Keystore password') self.show_error(_('Failed to update PS Keystore password')) return if psman.is_ps_ks_encrypted(): msg = _('Password was updated successfully') pwd_btn_text = _('Change Password') else: msg = _('Password is disabled, this wallet is not protected') pwd_btn_text = _('Set Password') self.show_message(msg, title=_("Success")) self.password_btn.setText(pwd_btn_text)
def request_safe_t_init_settings(self, wizard, method, device): vbox = QVBoxLayout() next_enabled = True label = QLabel(_("Enter a label to name your device:")) name = QLineEdit() hl = QHBoxLayout() hl.addWidget(label) hl.addWidget(name) hl.addStretch(1) vbox.addLayout(hl) def clean_text(widget): text = widget.toPlainText().strip() return ' '.join(text.split()) if method in [TIM_NEW, TIM_RECOVER]: gb = QGroupBox() hbox1 = QHBoxLayout() gb.setLayout(hbox1) vbox.addWidget(gb) gb.setTitle(_("Select your seed length:")) bg = QButtonGroup() for i, count in enumerate([12, 18, 24]): rb = QRadioButton(gb) rb.setText(_("{:d} words").format(count)) bg.addButton(rb) bg.setId(rb, i) hbox1.addWidget(rb) rb.setChecked(True) cb_pin = QCheckBox(_('Enable PIN protection')) cb_pin.setChecked(True) else: text = QTextEdit() text.setMaximumHeight(60) if method == TIM_MNEMONIC: msg = _("Enter your BIP39 mnemonic:") else: msg = _("Enter the master private key beginning with xprv:") def set_enabled(): from electrum_firo.bip32 import is_xprv wizard.next_button.setEnabled(is_xprv(clean_text(text))) text.textChanged.connect(set_enabled) next_enabled = False vbox.addWidget(QLabel(msg)) vbox.addWidget(text) pin = QLineEdit() pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}'))) pin.setMaximumWidth(100) hbox_pin = QHBoxLayout() hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) hbox_pin.addWidget(pin) hbox_pin.addStretch(1) if method in [TIM_NEW, TIM_RECOVER]: vbox.addWidget(WWLabel(RECOMMEND_PIN)) vbox.addWidget(cb_pin) else: vbox.addLayout(hbox_pin) passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT) passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN) passphrase_warning.setStyleSheet("color: red") cb_phrase = QCheckBox(_('Enable passphrases')) cb_phrase.setChecked(False) vbox.addWidget(passphrase_msg) vbox.addWidget(passphrase_warning) vbox.addWidget(cb_phrase) wizard.exec_layout(vbox, next_enabled=next_enabled) if method in [TIM_NEW, TIM_RECOVER]: item = bg.checkedId() pin = cb_pin.isChecked() else: item = ' '.join(str(clean_text(text)).split()) pin = str(pin.text()) return (item, name.text(), pin, cb_phrase.isChecked())
def slider_moved(): mins = timeout_slider.sliderPosition() timeout_minutes.setText(_("{:2d} minutes").format(mins))
def __init__(self, window, plugin, keystore, device_id): title = _("{} Settings").format(plugin.device) super(SettingsDialog, self).__init__(window, title) self.setMaximumWidth(540) devmgr = plugin.device_manager() config = devmgr.config handler = keystore.handler thread = keystore.thread hs_cols, hs_rows = (128, 64) def invoke_client(method, *args, **kw_args): unpair_after = kw_args.pop('unpair_after', False) def task(): client = devmgr.client_by_id(device_id) if not client: raise RuntimeError("Device not connected") if method: getattr(client, method)(*args, **kw_args) if unpair_after: devmgr.unpair_id(device_id) return client.features thread.add(task, on_success=update) def update(features): self.features = features set_label_enabled() if features.bootloader_hash: bl_hash = bh2u(features.bootloader_hash) bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]]) else: bl_hash = "N/A" noyes = [_("No"), _("Yes")] endis = [_("Enable Passphrases"), _("Disable Passphrases")] disen = [_("Disabled"), _("Enabled")] setchange = [_("Set a PIN"), _("Change PIN")] version = "%d.%d.%d" % (features.major_version, features.minor_version, features.patch_version) device_label.setText(features.label) pin_set_label.setText(noyes[features.pin_protection]) passphrases_label.setText(disen[features.passphrase_protection]) bl_hash_label.setText(bl_hash) label_edit.setText(features.label) device_id_label.setText(features.device_id) initialized_label.setText(noyes[features.initialized]) version_label.setText(version) clear_pin_button.setVisible(features.pin_protection) clear_pin_warning.setVisible(features.pin_protection) pin_button.setText(setchange[features.pin_protection]) pin_msg.setVisible(not features.pin_protection) passphrase_button.setText(endis[features.passphrase_protection]) language_label.setText(features.language) def set_label_enabled(): label_apply.setEnabled(label_edit.text() != self.features.label) def rename(): invoke_client('change_label', label_edit.text()) def toggle_passphrase(): title = _("Confirm Toggle Passphrase Protection") currently_enabled = self.features.passphrase_protection if currently_enabled: msg = _("After disabling passphrases, you can only pair this " "Firo Electrum wallet if it had an empty passphrase. " "If its passphrase was not empty, you will need to " "create a new wallet with the install wizard. You " "can use this wallet again at any time by re-enabling " "passphrases and entering its passphrase.") else: msg = _("Your current Firo Electrum wallet can only be used with " "an empty passphrase. You must create a separate " "wallet with the install wizard for other passphrases " "as each one generates a new set of addresses.") msg += "\n\n" + _("Are you sure you want to proceed?") if not self.question(msg, title=title): return invoke_client('toggle_passphrase', unpair_after=currently_enabled) def change_homescreen(): filename = getOpenFileName( parent=self, title=_("Choose Homescreen"), config=config, ) if not filename: return # user cancelled if filename.endswith('.toif'): img = open(filename, 'rb').read() if img[:8] != b'TOIf\x90\x00\x90\x00': handler.show_error('File is not a TOIF file with size of 144x144') return else: from PIL import Image # FIXME im = Image.open(filename) if im.size != (128, 64): handler.show_error('Image must be 128 x 64 pixels') return im = im.convert('1') pix = im.load() img = bytearray(1024) for j in range(64): for i in range(128): if pix[i, j]: o = (i + j * 128) img[o // 8] |= (1 << (7 - o % 8)) img = bytes(img) invoke_client('change_homescreen', img) def clear_homescreen(): invoke_client('change_homescreen', b'\x00') def set_pin(): invoke_client('set_pin', remove=False) def clear_pin(): invoke_client('set_pin', remove=True) def wipe_device(): wallet = window.wallet if wallet and sum(wallet.get_balance()): title = _("Confirm Device Wipe") msg = _("Are you SURE you want to wipe the device?\n" "Your wallet still has Dash coins in it!") if not self.question(msg, title=title, icon=QMessageBox.Critical): return invoke_client('wipe_device', unpair_after=True) def slider_moved(): mins = timeout_slider.sliderPosition() timeout_minutes.setText(_("{:2d} minutes").format(mins)) def slider_released(): config.set_session_timeout(timeout_slider.sliderPosition() * 60) # Information tab info_tab = QWidget() info_layout = QVBoxLayout(info_tab) info_glayout = QGridLayout() info_glayout.setColumnStretch(2, 1) device_label = QLabel() pin_set_label = QLabel() passphrases_label = QLabel() version_label = QLabel() device_id_label = QLabel() bl_hash_label = QLabel() bl_hash_label.setWordWrap(True) language_label = QLabel() initialized_label = QLabel() rows = [ (_("Device Label"), device_label), (_("PIN set"), pin_set_label), (_("Passphrases"), passphrases_label), (_("Firmware Version"), version_label), (_("Device ID"), device_id_label), (_("Bootloader Hash"), bl_hash_label), (_("Language"), language_label), (_("Initialized"), initialized_label), ] for row_num, (label, widget) in enumerate(rows): info_glayout.addWidget(QLabel(label), row_num, 0) info_glayout.addWidget(widget, row_num, 1) info_layout.addLayout(info_glayout) # Settings tab settings_tab = QWidget() settings_layout = QVBoxLayout(settings_tab) settings_glayout = QGridLayout() # Settings tab - Label label_msg = QLabel(_("Name this {}. If you have multiple devices " "their labels help distinguish them.") .format(plugin.device)) label_msg.setWordWrap(True) label_label = QLabel(_("Device Label")) label_edit = QLineEdit() label_edit.setMinimumWidth(150) label_edit.setMaxLength(plugin.MAX_LABEL_LEN) label_apply = QPushButton(_("Apply")) label_apply.clicked.connect(rename) label_edit.textChanged.connect(set_label_enabled) settings_glayout.addWidget(label_label, 0, 0) settings_glayout.addWidget(label_edit, 0, 1, 1, 2) settings_glayout.addWidget(label_apply, 0, 3) settings_glayout.addWidget(label_msg, 1, 1, 1, -1) # Settings tab - PIN pin_label = QLabel(_("PIN Protection")) pin_button = QPushButton() pin_button.clicked.connect(set_pin) settings_glayout.addWidget(pin_label, 2, 0) settings_glayout.addWidget(pin_button, 2, 1) pin_msg = QLabel(_("PIN protection is strongly recommended. " "A PIN is your only protection against someone " "stealing your Dash coins if they obtain physical " "access to your {}.").format(plugin.device)) pin_msg.setWordWrap(True) pin_msg.setStyleSheet("color: red") settings_glayout.addWidget(pin_msg, 3, 1, 1, -1) # Settings tab - Homescreen homescreen_label = QLabel(_("Homescreen")) homescreen_change_button = QPushButton(_("Change...")) homescreen_clear_button = QPushButton(_("Reset")) homescreen_change_button.clicked.connect(change_homescreen) try: import PIL except ImportError: homescreen_change_button.setDisabled(True) homescreen_change_button.setToolTip( _("Required package 'PIL' is not available - Please install it.") ) homescreen_clear_button.clicked.connect(clear_homescreen) homescreen_msg = QLabel(_("You can set the homescreen on your " "device to personalize it. You must " "choose a {} x {} monochrome black and " "white image.").format(hs_cols, hs_rows)) homescreen_msg.setWordWrap(True) settings_glayout.addWidget(homescreen_label, 4, 0) settings_glayout.addWidget(homescreen_change_button, 4, 1) settings_glayout.addWidget(homescreen_clear_button, 4, 2) settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1) # Settings tab - Session Timeout timeout_label = QLabel(_("Session Timeout")) timeout_minutes = QLabel() timeout_slider = QSlider(Qt.Horizontal) timeout_slider.setRange(1, 60) timeout_slider.setSingleStep(1) timeout_slider.setTickInterval(5) timeout_slider.setTickPosition(QSlider.TicksBelow) timeout_slider.setTracking(True) timeout_msg = QLabel( _("Clear the session after the specified period " "of inactivity. Once a session has timed out, " "your PIN and passphrase (if enabled) must be " "re-entered to use the device.")) timeout_msg.setWordWrap(True) timeout_slider.setSliderPosition(config.get_session_timeout() // 60) slider_moved() timeout_slider.valueChanged.connect(slider_moved) timeout_slider.sliderReleased.connect(slider_released) settings_glayout.addWidget(timeout_label, 6, 0) settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3) settings_glayout.addWidget(timeout_minutes, 6, 4) settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1) settings_layout.addLayout(settings_glayout) settings_layout.addStretch(1) # Advanced tab advanced_tab = QWidget() advanced_layout = QVBoxLayout(advanced_tab) advanced_glayout = QGridLayout() # Advanced tab - clear PIN clear_pin_button = QPushButton(_("Disable PIN")) clear_pin_button.clicked.connect(clear_pin) clear_pin_warning = QLabel( _("If you disable your PIN, anyone with physical access to your " "{} device can spend your Dash coins.").format(plugin.device)) clear_pin_warning.setWordWrap(True) clear_pin_warning.setStyleSheet("color: red") advanced_glayout.addWidget(clear_pin_button, 0, 2) advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5) # Advanced tab - toggle passphrase protection passphrase_button = QPushButton() passphrase_button.clicked.connect(toggle_passphrase) passphrase_msg = WWLabel(PASSPHRASE_HELP) passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN) passphrase_warning.setStyleSheet("color: red") advanced_glayout.addWidget(passphrase_button, 3, 2) advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5) advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5) # Advanced tab - wipe device wipe_device_button = QPushButton(_("Wipe Device")) wipe_device_button.clicked.connect(wipe_device) wipe_device_msg = QLabel( _("Wipe the device, removing all data from it. The firmware " "is left unchanged.")) wipe_device_msg.setWordWrap(True) wipe_device_warning = QLabel( _("Only wipe a device if you have the recovery seed written down " "and the device wallet(s) are empty, otherwise the Dash coins " "will be lost forever.")) wipe_device_warning.setWordWrap(True) wipe_device_warning.setStyleSheet("color: red") advanced_glayout.addWidget(wipe_device_button, 6, 2) advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5) advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5) advanced_layout.addLayout(advanced_glayout) advanced_layout.addStretch(1) tabs = QTabWidget(self) tabs.addTab(info_tab, _("Information")) tabs.addTab(settings_tab, _("Settings")) tabs.addTab(advanced_tab, _("Advanced")) dialog_vbox = QVBoxLayout(self) dialog_vbox.addWidget(tabs) dialog_vbox.addLayout(Buttons(CloseButton(self))) # Update information invoke_client(None)
QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget, QMessageBox, QFileDialog, QSlider, QTabWidget) from electrum_firo.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton, OkButton, CloseButton, getOpenFileName) from electrum_firo.i18n import _ from electrum_firo.plugin import hook from electrum_firo.util import bh2u from ..hw_wallet.qt import QtHandlerBase, QtPluginBase from ..hw_wallet.plugin import only_hook_if_libraries_available from .safe_t import SafeTPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC PASSPHRASE_HELP_SHORT =_( "Passphrases allow you to access new wallets, each " "hidden behind a particular case-sensitive passphrase.") PASSPHRASE_HELP = PASSPHRASE_HELP_SHORT + " " + _( "You need to create a separate Firo Electrum wallet for each passphrase " "you use as they each generate different addresses. Changing " "your passphrase does not lose other wallets, each is still " "accessible behind its own passphrase.") RECOMMEND_PIN = _( "You should enable PIN protection. Your PIN is the only protection " "for your Dash coins if your device is lost or stolen.") PASSPHRASE_NOT_PIN = _( "If you forget a passphrase you will be unable to access any " "Dash coins in the wallet behind it. A passphrase is not a PIN. " "Only change this if you are sure you understand it.")
from electrum_firo.i18n import _ fullname = 'Ledger Wallet' description = 'Provides support for Ledger hardware wallet' requires = [('btchip', 'github.com/ledgerhq/btchip-python')] registers_keystore = ('hardware', 'ledger', _("Ledger wallet")) available_for = ['qt', 'cmdline']