|
@ -261,29 +261,69 @@ def qt_plugin_class(base_plugin_class): |
|
|
lambda: self.show_address(wallet, addrs[0])) |
|
|
lambda: self.show_address(wallet, addrs[0])) |
|
|
|
|
|
|
|
|
def settings_dialog(self, window): |
|
|
def settings_dialog(self, window): |
|
|
dialog = SettingsDialog(window, self) |
|
|
hid_id = self.choose_device(window) |
|
|
window.wallet.handler.exec_dialog(dialog) |
|
|
if hid_id: |
|
|
|
|
|
dialog = SettingsDialog(window, self, hid_id) |
|
|
|
|
|
window.wallet.handler.exec_dialog(dialog) |
|
|
|
|
|
|
|
|
|
|
|
def choose_device(self, window): |
|
|
|
|
|
'''This dialog box should be usable even if the user has |
|
|
|
|
|
forgotten their PIN or it is in bootloader mode.''' |
|
|
|
|
|
handler = window.wallet.handler |
|
|
|
|
|
hid_id = self.device_manager().wallet_hid_id(window.wallet) |
|
|
|
|
|
if not hid_id: |
|
|
|
|
|
clients, labels = self.unpaired_clients(handler) |
|
|
|
|
|
if clients: |
|
|
|
|
|
msg = _("Select a %s device:") % self.device |
|
|
|
|
|
choice = self.query_choice(window, msg, labels) |
|
|
|
|
|
if choice is not None: |
|
|
|
|
|
hid_id = clients[choice].hid_id() |
|
|
|
|
|
else: |
|
|
|
|
|
handler.show_error(_("No devices found")) |
|
|
|
|
|
return hid_id |
|
|
|
|
|
|
|
|
|
|
|
def query_choice(self, window, msg, choices): |
|
|
|
|
|
dialog = WindowModalDialog(window) |
|
|
|
|
|
clayout = ChoicesLayout(msg, choices) |
|
|
|
|
|
layout = clayout.layout() |
|
|
|
|
|
layout.addStretch(1) |
|
|
|
|
|
layout.addLayout(Buttons(CancelButton(dialog), OkButton(dialog))) |
|
|
|
|
|
dialog.setLayout(layout) |
|
|
|
|
|
if not dialog.exec_(): |
|
|
|
|
|
return None |
|
|
|
|
|
return clayout.selected_index() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return QtPlugin |
|
|
return QtPlugin |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SettingsDialog(WindowModalDialog): |
|
|
class SettingsDialog(WindowModalDialog): |
|
|
|
|
|
'''This dialog doesn't require a device be paired with a wallet. |
|
|
|
|
|
We want users to be able to wipe a device even if they've forgotten |
|
|
|
|
|
their PIN.''' |
|
|
|
|
|
|
|
|
def __init__(self, window, plugin): |
|
|
def __init__(self, window, plugin, hid_id): |
|
|
self.plugin = plugin |
|
|
|
|
|
self.window = window # The main electrum window |
|
|
|
|
|
title = _("%s Settings") % plugin.device |
|
|
title = _("%s Settings") % plugin.device |
|
|
super(SettingsDialog, self).__init__(window, title) |
|
|
super(SettingsDialog, self).__init__(window, title) |
|
|
self.setMaximumWidth(540) |
|
|
self.setMaximumWidth(540) |
|
|
|
|
|
|
|
|
|
|
|
devmgr = plugin.device_manager() |
|
|
|
|
|
handler = window.wallet.handler |
|
|
|
|
|
# wallet can be None, needn't be window.wallet |
|
|
|
|
|
wallet = devmgr.wallet_by_hid_id(hid_id) |
|
|
hs_rows, hs_cols = (64, 128) |
|
|
hs_rows, hs_cols = (64, 128) |
|
|
|
|
|
|
|
|
def get_client(lookup=DeviceMgr.PAIRED): |
|
|
def get_client(): |
|
|
return self.plugin.get_client(wallet, lookup) |
|
|
client = devmgr.client_by_hid_id(hid_id, handler) |
|
|
|
|
|
if not client: |
|
|
|
|
|
self.show_error("Device not connected!") |
|
|
|
|
|
raise RuntimeError("Device not connected") |
|
|
|
|
|
return client |
|
|
|
|
|
|
|
|
def update(): |
|
|
def update(): |
|
|
features = get_client(DeviceMgr.PAIRED).features |
|
|
# self.features for outer scopes |
|
|
self.features = features |
|
|
client = get_client() |
|
|
# The above was for outer scopes. Now the real logic. |
|
|
features = self.features = client.features |
|
|
set_label_enabled() |
|
|
set_label_enabled() |
|
|
bl_hash = features.bootloader_hash.encode('hex') |
|
|
bl_hash = features.bootloader_hash.encode('hex') |
|
|
bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]]) |
|
|
bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]]) |
|
@ -301,6 +341,7 @@ class SettingsDialog(WindowModalDialog): |
|
|
bl_hash_label.setText(bl_hash) |
|
|
bl_hash_label.setText(bl_hash) |
|
|
label_edit.setText(features.label) |
|
|
label_edit.setText(features.label) |
|
|
device_id_label.setText(features.device_id) |
|
|
device_id_label.setText(features.device_id) |
|
|
|
|
|
serial_number_label.setText(client.hid_id()) |
|
|
initialized_label.setText(noyes[features.initialized]) |
|
|
initialized_label.setText(noyes[features.initialized]) |
|
|
version_label.setText(version) |
|
|
version_label.setText(version) |
|
|
coins_label.setText(coins) |
|
|
coins_label.setText(coins) |
|
@ -309,7 +350,6 @@ class SettingsDialog(WindowModalDialog): |
|
|
pin_button.setText(setchange[features.pin_protection]) |
|
|
pin_button.setText(setchange[features.pin_protection]) |
|
|
pin_msg.setVisible(not features.pin_protection) |
|
|
pin_msg.setVisible(not features.pin_protection) |
|
|
passphrase_button.setText(endis[features.passphrase_protection]) |
|
|
passphrase_button.setText(endis[features.passphrase_protection]) |
|
|
|
|
|
|
|
|
language_label.setText(features.language) |
|
|
language_label.setText(features.language) |
|
|
|
|
|
|
|
|
def set_label_enabled(): |
|
|
def set_label_enabled(): |
|
@ -331,7 +371,7 @@ class SettingsDialog(WindowModalDialog): |
|
|
if not self.question(msg, title=title): |
|
|
if not self.question(msg, title=title): |
|
|
return |
|
|
return |
|
|
get_client().toggle_passphrase() |
|
|
get_client().toggle_passphrase() |
|
|
self.device_manager().close_wallet(wallet) # Unpair |
|
|
devmgr.unpair(hid_id) |
|
|
update() |
|
|
update() |
|
|
|
|
|
|
|
|
def change_homescreen(): |
|
|
def change_homescreen(): |
|
@ -362,27 +402,25 @@ class SettingsDialog(WindowModalDialog): |
|
|
set_pin(remove=True) |
|
|
set_pin(remove=True) |
|
|
|
|
|
|
|
|
def wipe_device(): |
|
|
def wipe_device(): |
|
|
# FIXME: cannot yet wipe a device that is only plugged in |
|
|
if wallet and sum(wallet.get_balance()): |
|
|
if sum(wallet.get_balance()): |
|
|
|
|
|
title = _("Confirm Device Wipe") |
|
|
title = _("Confirm Device Wipe") |
|
|
msg = _("Are you SURE you want to wipe the device?\n" |
|
|
msg = _("Are you SURE you want to wipe the device?\n" |
|
|
"Your wallet still has bitcoins in it!") |
|
|
"Your wallet still has bitcoins in it!") |
|
|
if not self.question(msg, title=title, |
|
|
if not self.question(msg, title=title, |
|
|
icon=QMessageBox.Critical): |
|
|
icon=QMessageBox.Critical): |
|
|
return |
|
|
return |
|
|
# Note: we use PRESENT so that a user who has forgotten |
|
|
get_client().wipe_device() |
|
|
# their PIN is not prevented from wiping their device |
|
|
devmgr.unpair(hid_id) |
|
|
get_client(DeviceMgr.PRESENT).wipe_device() |
|
|
|
|
|
self.device_manager().close_wallet(wallet) |
|
|
|
|
|
update() |
|
|
update() |
|
|
|
|
|
|
|
|
def slider_moved(): |
|
|
def slider_moved(): |
|
|
mins = timeout_slider.sliderPosition() |
|
|
mins = timeout_slider.sliderPosition() |
|
|
timeout_minutes.setText(_("%2d minutes") % mins) |
|
|
timeout_minutes.setText(_("%2d minutes") % mins) |
|
|
|
|
|
|
|
|
wallet = window.wallet |
|
|
def slider_released(): |
|
|
handler = wallet.handler |
|
|
seconds = timeout_slider.sliderPosition() * 60 |
|
|
device = plugin.device |
|
|
wallet.set_session_timeout(seconds) |
|
|
|
|
|
|
|
|
dialog_vbox = QVBoxLayout(self) |
|
|
dialog_vbox = QVBoxLayout(self) |
|
|
|
|
|
|
|
|
# Information tab |
|
|
# Information tab |
|
@ -394,6 +432,7 @@ class SettingsDialog(WindowModalDialog): |
|
|
pin_set_label = QLabel() |
|
|
pin_set_label = QLabel() |
|
|
version_label = QLabel() |
|
|
version_label = QLabel() |
|
|
device_id_label = QLabel() |
|
|
device_id_label = QLabel() |
|
|
|
|
|
serial_number_label = QLabel() |
|
|
bl_hash_label = QLabel() |
|
|
bl_hash_label = QLabel() |
|
|
bl_hash_label.setWordWrap(True) |
|
|
bl_hash_label.setWordWrap(True) |
|
|
coins_label = QLabel() |
|
|
coins_label = QLabel() |
|
@ -404,7 +443,8 @@ class SettingsDialog(WindowModalDialog): |
|
|
(_("Device Label"), device_label), |
|
|
(_("Device Label"), device_label), |
|
|
(_("PIN set"), pin_set_label), |
|
|
(_("PIN set"), pin_set_label), |
|
|
(_("Firmware Version"), version_label), |
|
|
(_("Firmware Version"), version_label), |
|
|
(_("Serial Number"), device_id_label), |
|
|
(_("Device ID"), device_id_label), |
|
|
|
|
|
(_("Serial Number"), serial_number_label), |
|
|
(_("Bootloader Hash"), bl_hash_label), |
|
|
(_("Bootloader Hash"), bl_hash_label), |
|
|
(_("Supported Coins"), coins_label), |
|
|
(_("Supported Coins"), coins_label), |
|
|
(_("Language"), language_label), |
|
|
(_("Language"), language_label), |
|
@ -419,7 +459,6 @@ class SettingsDialog(WindowModalDialog): |
|
|
settings_tab = QWidget() |
|
|
settings_tab = QWidget() |
|
|
settings_layout = QVBoxLayout(settings_tab) |
|
|
settings_layout = QVBoxLayout(settings_tab) |
|
|
settings_glayout = QGridLayout() |
|
|
settings_glayout = QGridLayout() |
|
|
#settings_glayout.setColumnStretch(3, 1) |
|
|
|
|
|
|
|
|
|
|
|
# Settings tab - Label |
|
|
# Settings tab - Label |
|
|
label_msg = QLabel(_("Name this %s. If you have mutiple devices " |
|
|
label_msg = QLabel(_("Name this %s. If you have mutiple devices " |
|
@ -429,7 +468,7 @@ class SettingsDialog(WindowModalDialog): |
|
|
label_label = QLabel(_("Device Label")) |
|
|
label_label = QLabel(_("Device Label")) |
|
|
label_edit = QLineEdit() |
|
|
label_edit = QLineEdit() |
|
|
label_edit.setMinimumWidth(150) |
|
|
label_edit.setMinimumWidth(150) |
|
|
label_edit.setMaxLength(self.plugin.MAX_LABEL_LEN) |
|
|
label_edit.setMaxLength(plugin.MAX_LABEL_LEN) |
|
|
label_apply = QPushButton(_("Apply")) |
|
|
label_apply = QPushButton(_("Apply")) |
|
|
label_apply.clicked.connect(rename) |
|
|
label_apply.clicked.connect(rename) |
|
|
label_edit.textChanged.connect(set_label_enabled) |
|
|
label_edit.textChanged.connect(set_label_enabled) |
|
@ -451,7 +490,6 @@ class SettingsDialog(WindowModalDialog): |
|
|
pin_msg.setWordWrap(True) |
|
|
pin_msg.setWordWrap(True) |
|
|
pin_msg.setStyleSheet("color: red") |
|
|
pin_msg.setStyleSheet("color: red") |
|
|
settings_glayout.addWidget(pin_msg, 3, 1, 1, -1) |
|
|
settings_glayout.addWidget(pin_msg, 3, 1, 1, -1) |
|
|
settings_layout.addLayout(settings_glayout) |
|
|
|
|
|
|
|
|
|
|
|
# Settings tab - Homescreen |
|
|
# Settings tab - Homescreen |
|
|
homescreen_layout = QHBoxLayout() |
|
|
homescreen_layout = QHBoxLayout() |
|
@ -471,25 +509,31 @@ class SettingsDialog(WindowModalDialog): |
|
|
settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1) |
|
|
settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1) |
|
|
|
|
|
|
|
|
# Settings tab - Session Timeout |
|
|
# Settings tab - Session Timeout |
|
|
timeout_label = QLabel(_("Session Timeout")) |
|
|
if wallet: |
|
|
timeout_minutes = QLabel() |
|
|
timeout_label = QLabel(_("Session Timeout")) |
|
|
timeout_slider = self.slider = QSlider(Qt.Horizontal) |
|
|
timeout_minutes = QLabel() |
|
|
timeout_slider.setRange(1, 60) |
|
|
timeout_slider = QSlider(Qt.Horizontal) |
|
|
timeout_slider.setSingleStep(1) |
|
|
timeout_slider.setRange(1, 60) |
|
|
timeout_slider.setSliderPosition(wallet.session_timeout // 60) |
|
|
timeout_slider.setSingleStep(1) |
|
|
timeout_slider.setTickInterval(5) |
|
|
timeout_slider.setTickInterval(5) |
|
|
timeout_slider.setTickPosition(QSlider.TicksBelow) |
|
|
timeout_slider.setTickPosition(QSlider.TicksBelow) |
|
|
timeout_slider.setTracking(True) |
|
|
timeout_slider.setTracking(True) |
|
|
timeout_slider.valueChanged.connect(slider_moved) |
|
|
timeout_msg = QLabel( |
|
|
timeout_msg = QLabel(_("Clear the session after the specified period " |
|
|
_("Clear the session after the specified period " |
|
|
"of inactivity. Once a session has timed out, " |
|
|
"of inactivity. Once a session has timed out, " |
|
|
"your PIN and passphrase (if enabled) must be " |
|
|
"your PIN and passphrase (if enabled) must be " |
|
|
"re-entered to use the device.")) |
|
|
"re-entered to use the device.")) |
|
|
timeout_msg.setWordWrap(True) |
|
|
timeout_msg.setWordWrap(True) |
|
|
settings_glayout.addWidget(timeout_label, 6, 0) |
|
|
timeout_slider.setSliderPosition(wallet.session_timeout // 60) |
|
|
settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3) |
|
|
slider_moved() |
|
|
settings_glayout.addWidget(timeout_minutes, 6, 4) |
|
|
timeout_slider.valueChanged.connect(slider_moved) |
|
|
settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1) |
|
|
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 |
|
|
advanced_tab = QWidget() |
|
|
advanced_tab = QWidget() |
|
@ -499,9 +543,9 @@ class SettingsDialog(WindowModalDialog): |
|
|
# Advanced tab - clear PIN |
|
|
# Advanced tab - clear PIN |
|
|
clear_pin_button = QPushButton(_("Disable PIN")) |
|
|
clear_pin_button = QPushButton(_("Disable PIN")) |
|
|
clear_pin_button.clicked.connect(clear_pin) |
|
|
clear_pin_button.clicked.connect(clear_pin) |
|
|
clear_pin_warning = QLabel(_("If you disable your PIN, anyone with " |
|
|
clear_pin_warning = QLabel( |
|
|
"physical access to your %s device can " |
|
|
_("If you disable your PIN, anyone with physical access to your " |
|
|
"spend your bitcoins.") % plugin.device) |
|
|
"%s device can spend your bitcoins.") % plugin.device) |
|
|
clear_pin_warning.setWordWrap(True) |
|
|
clear_pin_warning.setWordWrap(True) |
|
|
clear_pin_warning.setStyleSheet("color: red") |
|
|
clear_pin_warning.setStyleSheet("color: red") |
|
|
advanced_glayout.addWidget(clear_pin_button, 0, 2) |
|
|
advanced_glayout.addWidget(clear_pin_button, 0, 2) |
|
@ -552,14 +596,7 @@ class SettingsDialog(WindowModalDialog): |
|
|
tabs.addTab(settings_tab, _("Settings")) |
|
|
tabs.addTab(settings_tab, _("Settings")) |
|
|
tabs.addTab(advanced_tab, _("Advanced")) |
|
|
tabs.addTab(advanced_tab, _("Advanced")) |
|
|
|
|
|
|
|
|
# Update information and then connect change slots |
|
|
# Update information |
|
|
update() |
|
|
update() |
|
|
slider_moved() |
|
|
|
|
|
|
|
|
|
|
|
dialog_vbox.addWidget(tabs) |
|
|
dialog_vbox.addWidget(tabs) |
|
|
dialog_vbox.addLayout(Buttons(CloseButton(self))) |
|
|
dialog_vbox.addLayout(Buttons(CloseButton(self))) |
|
|
|
|
|
|
|
|
def closeEvent(self, event): |
|
|
|
|
|
seconds = self.slider.sliderPosition() * 60 |
|
|
|
|
|
self.window.wallet.set_session_timeout(seconds) |
|
|
|
|
|
event.accept() |
|
|
|
|
|