Welcome Screen: Improve filesystem repair dialogs
[tails.git] / config / chroot_local-includes / usr / lib / python3 / dist-packages / tailsgreeter / ui / main_window.py
blobbb68dd71ef22712474e7a8ba91b46ac25324c50a
2 # Copyright 2015-2016 Tails developers <tails@boum.org>
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>
17 import logging
18 import threading
19 from typing import TYPE_CHECKING
20 import gi
21 import os
23 import tailsgreeter # NOQA: E402
24 from tailsgreeter import config # NOQA: E402
25 from tailsgreeter.config import persistent_settings_dir
26 from tailsgreeter.errors import (
27 FeatureActivationFailedError,
28 PersistentStorageError,
29 WrongPassphraseError,
30 FilesystemErrorsLeftUncorrectedError,
31 IOErrorsDetectedError,
33 from tailsgreeter.settings import SettingNotFoundError
34 from tailsgreeter.translatable_window import TranslatableWindow
35 from tailsgreeter.ui.popover import Popover
36 from tailsgreeter.ui import _
37 from tailsgreeter.ui.add_settings_dialog import AddSettingsDialog
38 from tailsgreeter.ui.additional_settings import AdditionalSetting
39 from tailsgreeter.ui.message_dialog import MessageDialog
40 from tailsgreeter.ui.help_window import GreeterHelpWindow
41 from tailsgreeter.ui.region_settings import LocalizationSettingUI
42 from tailsgreeter import TRANSLATION_DOMAIN
43 from tps import InvalidBootDeviceErrorType
45 gi.require_version("Gdk", "3.0")
46 gi.require_version("Gtk", "3.0")
47 gi.require_version("Handy", "1")
48 from gi.repository import Gdk, Gtk, GdkPixbuf, Handy, GLib # noqa: E402
50 Handy.init()
52 if TYPE_CHECKING:
53 from tailsgreeter.settings.persistence import PersistentStorageSettings
54 from tailsgreeter.ui.settings_collection import GreeterSettingsCollection
57 MAIN_UI_FILE = "main.ui"
58 CSS_FILE = "greeter.css"
59 ICON_DIR = "icons/"
60 PREFERRED_WIDTH = 620
61 PREFERRED_HEIGHT = 470
64 class GreeterMainWindow(Gtk.Window, TranslatableWindow):
65 def __init__(
66 self,
67 greeter,
68 persistence_setting: "PersistentStorageSettings",
69 settings: "GreeterSettingsCollection",
71 Gtk.Window.__init__(self, title=_(tailsgreeter.APPLICATION_TITLE))
72 TranslatableWindow.__init__(self, self)
73 self.greeter = greeter
74 self.persistence_setting = persistence_setting
75 self.settings = settings
77 # Set the main_window attribute for the settings. This is required
78 # in order to allow the settings to trigger changes in the main
79 # window, for example showing an info bar.
80 for setting in self.settings:
81 setting.main_window = self
83 self.connect("delete-event", self.cb_window_delete_event, None)
84 self.set_position(Gtk.WindowPosition.CENTER)
86 # Load custom CSS
87 css_provider = Gtk.CssProvider()
88 css_provider.load_from_path(config.data_path + CSS_FILE)
89 Gtk.StyleContext.add_provider_for_screen(
90 Gdk.Screen.get_default(),
91 css_provider,
92 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
95 # Load UI interface definition
96 builder = Gtk.Builder()
97 builder.set_translation_domain(TRANSLATION_DOMAIN)
98 builder.add_from_file(config.data_path + MAIN_UI_FILE)
99 builder.connect_signals(self)
101 for widget in builder.get_objects():
102 # Store translations for the builder objects
103 self.store_translations(widget)
104 # Workaround Gtk bug #710888 - GtkInfoBar not shown after calling
105 # gtk_widget_show:
106 # https://bugzilla.gnome.org/show_bug.cgi?id=710888
107 if isinstance(widget, Gtk.InfoBar):
108 revealer = widget.get_template_child(Gtk.InfoBar, "revealer")
109 revealer.set_transition_type(Gtk.RevealerTransitionType.NONE)
111 self.box_language = builder.get_object("box_language")
112 self.box_language_header = builder.get_object("box_language_header")
113 self.box_main = builder.get_object("box_main")
114 self.box_settings = builder.get_object("box_settings")
115 self.box_settings_header = builder.get_object("box_settings_header")
116 self.box_settings_values = builder.get_object("box_settings_values")
117 self.box_storage = builder.get_object("box_storage")
118 self.box_storage_unlock = builder.get_object("box_storage_unlock")
119 self.box_storage_unlock_status = builder.get_object("box_storage_unlocked")
120 self.entry_storage_passphrase = builder.get_object("entry_storage_passphrase")
121 self.button_storagecreate_create = builder.get_object(
122 "button_storagecreate_create"
124 self.box_create_tps = builder.get_object("create_tps_box")
126 self.frame_language = builder.get_object("frame_language")
127 self.infobar_settings_loaded = builder.get_object("infobar_settings_loaded")
128 self.label_settings_default = builder.get_object("label_settings_default")
129 self.listbox_add_setting = builder.get_object("listbox_add_setting")
130 self.listbox_settings = builder.get_object("listbox_settings")
131 self.toolbutton_settings_add = builder.get_object("toolbutton_settings_add")
132 self.listbox_settings = builder.get_object("listbox_settings")
133 self.listbox_region = builder.get_object("listbox_region")
134 self.button_start = builder.get_object("button_start")
135 self.headerbar = builder.get_object("headerbar")
137 # Set preferred width
138 self.set_default_size(
139 min(Gdk.Screen.get_default().get_width(), PREFERRED_WIDTH),
140 min(Gdk.Screen.get_default().get_height(), PREFERRED_HEIGHT),
142 self.set_valign(Gtk.Align.START)
144 # Add our icon dir to icon theme
145 icon_theme = Gtk.IconTheme.get_default()
146 icon_theme.prepend_search_path(config.data_path + ICON_DIR)
148 # Add placeholder to settings ListBox
149 self.listbox_settings.set_placeholder(self.label_settings_default)
151 # Add children to ApplicationWindow
152 self.add(self.box_main)
153 self.set_titlebar(self.headerbar)
155 # Set keyboard focus chain
156 self._set_focus_chain()
158 # Add settings to region listbox
159 for setting in self.settings.region_settings:
160 logging.debug("Adding '%s' to region listbox", setting.id)
161 self.listbox_region.add(setting.listboxrow)
163 # Add settings dialog
164 self.dialog_add_setting = AddSettingsDialog(builder, self.settings)
165 self.dialog_add_setting.set_transient_for(self)
167 # Add confirm dialog
168 self.confirm_dialog = MessageDialog(
169 message_type=Gtk.MessageType.WARNING,
170 title=_("Persistent Storage Still Locked"),
171 text=_(
172 "Do you really want to start Tails without unlocking your Persistent Storage?"
174 cancel_label=_("Cancel"),
175 ok_label=_("Start Without Persistent Storage"),
177 self.confirm_dialog.set_transient_for(self)
179 # Setup keyboard accelerators
180 self._build_accelerators()
182 self.store_translations(self)
184 # Persistent Storage
185 self.tps_upgrade_failed = False
187 self.box_storage = builder.get_object("box_storage")
188 self.box_storagecreate = builder.get_object("box_storagecreate")
189 self.box_storage_unlock = builder.get_object("box_storage_unlock")
190 self.box_storage_unlock_status = builder.get_object("box_storage_unlock_status")
191 self.label_storage_unlock_status = builder.get_object(
192 "label_storage_unlock_status"
194 self.image_storage_unlock_failed = builder.get_object(
195 "image_storage_unlock_failed"
198 self.box_storage_error = builder.get_object("box_storage_error")
199 self.button_storage_unlock = builder.get_object("button_storage_unlock") # type: Gtk.Button
200 self.checkbutton_storage_show_passphrase = builder.get_object(
201 "checkbutton_storage_show_passphrase"
203 self.entry_storage_passphrase = builder.get_object("entry_storage_passphrase")
204 self.image_storage_state = builder.get_object("image_storage_state")
205 self.label_storage_error = builder.get_object("label_storage_error")
206 self.linkbutton_storage_readonly_help = builder.get_object(
207 "linkbutton_storage_readonly_help",
209 self.spinner_storage_unlock = builder.get_object("spinner_storage_unlock")
210 self.button_start = builder.get_object("button_start")
212 self.checkbutton_storage_show_passphrase.connect(
213 "toggled", self.cb_checkbutton_storage_show_passphrase_toggled
216 self.box_storage.set_focus_chain(
218 self.box_storage_unlock,
219 self.checkbutton_storage_show_passphrase,
223 is_created = self.persistence_setting.is_created
224 can_unlock = self.persistence_setting.can_unlock
225 self.box_storagecreate.set_visible(not is_created)
226 self.box_storage.set_visible(is_created)
228 if is_created:
229 self.box_storage_unlock.set_visible(can_unlock)
230 self.checkbutton_storage_show_passphrase.set_visible(can_unlock)
231 self.image_storage_state.set_visible(True)
232 self.entry_storage_passphrase.set_visible(can_unlock)
233 self.spinner_storage_unlock.set_visible(False)
234 self.linkbutton_storage_readonly_help.set_visible(False)
235 if not can_unlock and (
236 self.persistence_setting.error_type
237 == InvalidBootDeviceErrorType.READ_ONLY
239 self.label_storage_error.set_label(
241 "Impossible to unlock the Persistent Storage "
242 "because the USB stick is read-only.",
245 self.linkbutton_storage_readonly_help.set_visible(True)
246 self.box_storage_error.set_visible(not can_unlock)
248 # Utility methods
250 def _build_accelerators(self):
251 accelgroup = Gtk.AccelGroup()
252 self.add_accel_group(accelgroup)
253 for accel_key in [s.accel_key for s in self.settings if s.accel_key]:
254 accelgroup.connect(
255 accel_key,
256 Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.CONTROL_MASK,
257 Gtk.AccelFlags.VISIBLE,
258 self.cb_accelgroup_setting_activated,
261 def _set_focus_chain(self):
262 self.box_language.set_focus_chain(
263 [self.frame_language, self.box_language_header]
265 self.box_settings.set_focus_chain(
266 [self.box_settings_values, self.box_settings_header]
269 # Actions
271 def apply_settings(self):
272 for setting in self.settings:
273 setting.apply()
275 def load_settings(self):
276 # We have to load formats and keyboard before language, because
277 # changing the language also changes the other two, which causes
278 # the settings files to be overwritten. So we load the region
279 # settings in reversed order.
280 settings_loaded = False
281 for setting in reversed(list(self.settings.region_settings)):
282 try:
283 changed = setting.load()
284 if changed:
285 # We only want to show the "settings loaded" notification
286 # if settings were actually changed, i.e. the settings
287 # in the persistent settings dir were not the same as
288 # the already configured ones.
289 # Else, the notification would also be shown the first time
290 # the system is booted after creating the Persistent Storage
291 # (which currently means that the Persistent Storage is empty,
292 # but that's WIP on #11529), because then the persistent
293 # settings dir doesn't exist yet, which means that live-boot
294 # copies the current settings dir to the Persistent Storage -
295 # which contains the currently configured settings, which are
296 # then loaded.
297 settings_loaded = True
298 except SettingNotFoundError as e:
299 logging.debug(e)
300 # The settings file does not exist, so we create it by
301 # applying the setting's default value.
302 setting.apply()
304 for setting in self.settings.additional_settings:
305 try:
306 changed = setting.load()
307 # We only add the setting to the list of additional settings
308 # if it was actually changed. Else it is either already added or
309 # it has the default value.
310 if not changed:
311 continue
312 settings_loaded = True
313 # Add the setting to the listbox of added settings, if it was
314 # not added before (by the user, before unlocking perrsistence).
315 if self.setting_added(setting.id):
316 # The setting was already added, we only have to call apply()
317 # to update the label
318 setting.apply()
319 else:
320 self.add_setting(setting.id)
321 except SettingNotFoundError as e:
322 logging.debug(e)
323 # The settings file does not exist, so we create it by
324 # applying the setting's default value.
325 setting.apply()
327 if settings_loaded:
328 self.infobar_settings_loaded.set_visible(True)
330 def run_add_setting_dialog(self, id_=None):
331 response = self.dialog_add_setting.run(id_)
332 if response == Gtk.ResponseType.YES:
333 row = self.listbox_add_setting.get_selected_row()
334 id_ = self.settings.id_from_row(row)
335 setting = self.settings.additional_settings[id_]
337 self.dialog_add_setting.set_visible(False)
338 self.dialog_add_setting.stack.remove(setting.box)
340 self.add_setting(id_)
341 else:
342 old_details = self.dialog_add_setting.stack.get_child_by_name(
343 "setting-details"
345 if old_details:
346 self.dialog_add_setting.stack.remove(old_details)
347 self.dialog_add_setting.set_visible(False)
349 def add_setting(self, id_):
350 logging.debug("Adding setting '%s'", id_)
351 setting = self.settings.additional_settings[id_]
352 setting.apply()
353 setting.build_popover()
355 self.listbox_add_setting.remove(setting.listboxrow)
356 self.listbox_settings.add(setting.listboxrow)
357 self.listbox_settings.unselect_all()
359 if not self.listbox_add_setting.get_children():
360 self.toolbutton_settings_add.set_sensitive(False)
362 def edit_setting(self, id_):
363 if self.settings[id_].has_popover():
364 self.settings[id_].listboxrow.emit("activate")
365 else:
366 self.run_add_setting_dialog(id_)
368 def setting_added(self, id_):
369 setting = self.settings.additional_settings[id_]
370 return setting.listboxrow in self.listbox_settings.get_children()
372 def show(self):
373 super().show()
374 self.button_start.grab_focus()
375 self.get_root_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.ARROW))
377 def unlock_tps(self, forceful_fsck: bool = False):
378 self.box_storage_unlock.set_visible(False)
379 self.label_storage_unlock_status.set_label(_("Unlocking…"))
380 self.label_storage_unlock_status.set_visible(True)
381 self.image_storage_unlock_failed.set_visible(False)
382 self.box_storage_unlock_status.set_visible(True)
383 self.checkbutton_storage_show_passphrase.set_visible(False)
384 self.image_storage_state.set_visible(False)
385 self.spinner_storage_unlock.set_visible(True)
387 passphrase = self.entry_storage_passphrase.get_text()
389 # Let's execute the unlocking in a thread
390 def do_unlock_tps(forceful_fsck: bool):
391 try:
392 # First, upgrade the storage if needed (skip if we're
393 # doing a forceful fsck, because then we already tried
394 # to upgrade)
395 if not self.persistence_setting.is_upgraded and not forceful_fsck:
396 try:
397 GLib.idle_add(self.on_tps_upgrading)
398 self.persistence_setting.upgrade_luks(passphrase)
399 except PersistentStorageError as e:
400 # We continue unlocking the storage even if the upgrade
401 # failed, but we display an error message
402 logging.error(e)
403 self.tps_upgrade_failed = True
405 # Then, unlock the storage
406 self.persistence_setting.unlock(passphrase, forceful_fsck)
407 GLib.idle_add(self.cb_tps_unlocked)
408 except WrongPassphraseError:
409 GLib.idle_add(self.cb_tps_unlock_failed_with_incorrect_passphrase)
410 except IOErrorsDetectedError:
411 GLib.idle_add(self.cb_tps_unlock_failed_with_io_errors)
412 except FilesystemErrorsLeftUncorrectedError:
413 if forceful_fsck:
414 # We already tried to unlock the storage with a forceful fsck
415 # and it still failed, so we give up
416 GLib.idle_add(self.on_tps_unlock_failed)
417 else:
418 GLib.idle_add(self.cb_unlock_failed_with_filesystem_errors)
419 except PersistentStorageError as e:
420 logging.error(e)
421 GLib.idle_add(self.on_tps_unlock_failed)
422 return
424 unlocking_thread = threading.Thread(target=do_unlock_tps, args=(forceful_fsck,))
425 unlocking_thread.start()
427 def repair_tps_filesystem(self):
428 dialog = MessageDialog(
429 message_type=Gtk.MessageType.INFO,
430 title=_("Repairing the File System"),
431 text=_("This may take a long time..."),
433 dialog.set_transient_for(self)
434 # Add a spinner to the dialog, next to the secondary label
435 box = Gtk.Box(spacing=6, margin=12)
436 spinner = Gtk.Spinner()
437 spinner.start()
438 box.pack_start(spinner, False, False, 0)
439 label = dialog.get_message_area().get_children()[1]
440 dialog.get_message_area().remove(label)
441 box.pack_start(label, False, False, 0)
442 box.show_all()
443 dialog.get_message_area().pack_start(box, False, False, 0)
445 def on_tps_repair_failed():
446 dialog.response(Gtk.ResponseType.CANCEL)
447 label = "{}\n\n{}".format(
448 _("Failed to repair the Persistent Storage file system."),
450 "Start Tails to send an error report and learn how to recover your data."
453 # XXX: Actually open WhisperBack and some documentation on
454 # how to recover data after Tails has started
455 self.on_tps_activation_failed(label)
457 def on_tps_repair_success():
458 dialog.response(Gtk.ResponseType.OK)
459 # Show a separate dialog to support closing it via Alt+F4 or Escape
460 dialog_ = MessageDialog(
461 message_type=Gtk.MessageType.INFO,
462 title=_("File System Repaired Successfully"),
463 text=_(
464 "It's possible that some data was lost during the repair. "
465 "Please check the contents of your Persistent Storage and "
466 "restore any lost data from a backup."
468 ok_label=_("Close"),
470 dialog_.set_transient_for(self)
471 dialog_.run()
472 dialog_.destroy()
474 def do_repair_tps_filesystem():
475 try:
476 self.persistence_setting.repair_filesystem()
477 GLib.idle_add(on_tps_repair_success)
478 except PersistentStorageError as e:
479 logging.error(e)
480 GLib.idle_add(on_tps_repair_failed)
482 repair_thread = threading.Thread(target=do_repair_tps_filesystem)
483 repair_thread.start()
485 # The response is DELETE_EVENT if the dialog is closed via the
486 # Escape key, in which case we want to keep the dialog open.
487 response = Gtk.ResponseType.DELETE_EVENT
488 while response == Gtk.ResponseType.DELETE_EVENT:
489 response = dialog.run()
490 dialog.destroy()
492 if response == Gtk.ResponseType.OK:
493 self.unlock_tps(forceful_fsck=True)
495 @staticmethod
496 def open_help_window(page: str) -> GreeterHelpWindow:
497 def localize_page(page: str) -> str:
498 """Try to get a localized version of the page"""
499 if config.current_language == "en":
500 return page
502 localized_page = page.replace(".en.", ".%s." % config.current_language)
504 # Strip the fragment identifier
505 index = localized_page.find("#")
506 filename = localized_page[:index] if index > 0 else localized_page
508 if os.path.isfile("/usr/share/doc/tails/website/" + filename):
509 return localized_page
510 return page
512 page = localize_page(page)
514 # Note that we add the "file://" part here, not in the URI.
515 # We're forced to add this
516 # callback *in addition* to the standard one (Gtk.show_uri),
517 # which will do nothing for uri:s without a protocol
518 # part. This is critical since we otherwise would open the
519 # default browser (iceweasel) in T-G. If pygtk had a mechanism
520 # like gtk's g_signal_handler_find() this could be dealt with
521 # in a less messy way by just removing the default handler.
522 uri = "file:///usr/share/doc/tails/website/" + page
523 logging.debug(f"Opening help window for {uri}")
524 helpwindow = GreeterHelpWindow(uri)
525 helpwindow.show()
526 return helpwindow
528 # Callbacks
530 def cb_accelgroup_setting_activated(
531 self, accel_group, accelerable, keyval, modifier
533 for setting in self.settings:
534 if setting.accel_key == keyval:
535 self.edit_setting(setting.id)
536 return False
538 def cb_linkbutton_help_activate(self, linkbutton, user_data=None):
539 linkbutton.set_sensitive(False)
540 # Display progress cursor and update the UI
541 self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
542 while Gtk.events_pending():
543 Gtk.main_iteration()
545 page = linkbutton.get_uri()
546 helpwindow = self.open_help_window(page)
548 def restore_linkbutton_status(widget, event, linkbutton):
549 linkbutton.set_sensitive(True)
550 return False
552 helpwindow.connect("delete-event", restore_linkbutton_status, linkbutton)
553 # Restore default cursor
554 self.get_window().set_cursor(None)
556 def cb_button_shutdown_clicked(self, widget, user_data=None):
557 self.greeter.shutdown()
558 return False
560 def cb_button_start_clicked(self, widget, user_data=None):
561 # Ask for confirmation when Persistent Storage exists but is not
562 # unlocked
563 if (
564 self.persistence_setting.is_created
565 and self.persistence_setting.can_unlock
566 and not self.persistence_setting.is_unlocked
567 and not self.persistence_setting.failed_with_unexpected_error
569 response = self.confirm_dialog.run()
570 self.confirm_dialog.set_visible(False)
571 if response != Gtk.ResponseType.OK:
572 return
574 self.greeter.login()
575 return False
577 def cb_button_storage_unlock_clicked(self, widget, user_data=None):
578 self.unlock_tps()
579 return False
581 def cb_entry_storage_passphrase_activated(self, entry, user_data=None):
582 # Don't try to unlock if the entry is empty
583 if not entry.get_text():
584 return False
586 self.unlock_tps()
587 return False
589 def cb_entry_storage_passphrase_changed(self, editable, user_data=None):
590 # Only allow starting if the password entry is empty. We used to
591 # attempt unlocking with the entered password when the "Start Tails"
592 # button was clicked, but changed that behavior (see #17136), so
593 # we now force users to click the "Unlock" button first before
594 # they can click "Start Tails".
595 passphrase_empty = not bool(editable.get_text())
596 self.button_start.set_sensitive(passphrase_empty)
597 self.button_storage_unlock.set_sensitive(not passphrase_empty)
598 return False
600 def cb_create_tps_switch_active_changed(self, widget, user_data=None):
601 self.greeter.persistent_storage_create.toggle()
603 def cb_infobar_close(self, infobar, user_data=None):
604 infobar.set_visible(False)
605 return False
607 def cb_infobar_response(self, infobar, response_id, user_data=None):
608 infobar.set_visible(False)
609 return False
611 def cb_listbox_add_setting_focus(self, widget, direction, user_data=None):
612 self.dialog_add_setting.listbox_focus()
613 return False
615 def cb_listbox_add_setting_row_activated(self, listbox, row, user_data=None):
616 self.dialog_add_setting.listbox_row_activated(row)
617 return False
619 def cb_listbox_region_row_activated(self, listbox, row, user_data=None):
620 setting = self.settings[self.settings.id_from_row(row)]
621 if not setting.popover.is_open():
622 setting.popover.open(self.on_region_setting_popover_closed, setting)
623 return False
625 def on_region_setting_popover_closed(
626 self, popover: Popover, setting: LocalizationSettingUI
628 # Unselect the listbox row
629 self.listbox_region.unselect_all()
631 if popover.response != Gtk.ResponseType.YES:
632 return
634 setting.apply()
636 def cb_listbox_settings_row_activated(self, listbox, row, user_data=None):
637 setting = self.settings[self.settings.id_from_row(row)]
638 if not setting.popover.is_open():
639 setting.popover.open(self.on_additional_setting_popover_closed, setting)
640 return False
642 def on_additional_setting_popover_closed(
643 self, popover: Popover, setting: AdditionalSetting
645 logging.debug("'%s' popover closed. response: %s", setting.id, popover.response)
646 # Unselect the listbox row
647 self.listbox_settings.unselect_all()
648 if popover.response == Gtk.ResponseType.YES:
649 setting.apply()
651 def cb_toolbutton_settings_add_clicked(self, user_data=None):
652 self.run_add_setting_dialog()
653 return False
655 def cb_toolbutton_settings_mnemonic_activate(self, widget, group_cycling):
656 self.run_add_setting_dialog()
657 return False
659 def cb_window_delete_event(self, widget, event, user_data=None):
660 # Don't close the toplevel window on user request (e.g. pressing
661 # Alt+F4)
662 return True
664 def cb_tps_unlocked(self):
665 logging.debug("Storage unlocked")
667 # Activate the Persistent Storage
668 try:
669 self.persistence_setting.activate_persistent_storage()
670 except FeatureActivationFailedError as e:
671 label = (
672 str(e)
673 + "\n"
674 + _(
675 "Start Tails and open the Persistent Storage settings to find out more."
678 self.on_tps_activation_failed(label)
679 return
680 except PersistentStorageError as e:
681 logging.error(e)
682 self.on_tps_activation_failed()
683 return
684 else:
685 if self.tps_upgrade_failed:
686 self.on_tps_upgrade_failed()
688 self.box_storage_unlock.set_visible(False)
689 self.spinner_storage_unlock.set_visible(False)
690 self.image_storage_state.set_from_icon_name(
691 "tails-unlocked", Gtk.IconSize.BUTTON
694 if not os.listdir(persistent_settings_dir):
695 self.apply_settings()
696 else:
697 self.load_settings()
699 # We're done unlocking and activating the Persistent Storage
700 self.image_storage_state.set_visible(True)
701 self.image_storage_unlock_failed.set_visible(False)
702 self.label_storage_unlock_status.set_label(
704 "Your Persistent Storage is unlocked. Its content will be available until you shut down Tails."
707 self.button_start.set_sensitive(True)
709 def cb_checkbutton_storage_show_passphrase_toggled(self, widget):
710 self.entry_storage_passphrase.set_visibility(widget.get_active())
712 def cb_tps_unlock_failed_with_incorrect_passphrase(self):
713 logging.debug("Storage unlock failed")
714 self.box_storage_unlock.set_visible(True)
715 self.checkbutton_storage_show_passphrase.set_visible(True)
716 self.image_storage_state.set_visible(True)
717 self.spinner_storage_unlock.set_visible(False)
718 self.label_storage_unlock_status.set_label(
719 _("Incorrect passphrase. Please try again."),
721 self.image_storage_unlock_failed.set_visible(True)
722 self.entry_storage_passphrase.select_region(0, -1)
723 self.entry_storage_passphrase.grab_focus()
725 def cb_tps_unlock_failed_with_io_errors(self):
726 logging.debug("Persistent Storage unlock failed due to IO errors")
728 label = "{}\n\n{}".format(
730 "Error reading data from your Persistent Storage. The hardware of your USB stick is probably failing."
732 _("Start Tails to learn how to recover your data."),
734 self.on_tps_activation_failed(label)
736 def cb_unlock_failed_with_filesystem_errors(self):
737 logging.debug("Persistent Storage unlock failed due to file system errors")
739 label = _("Failed to unlock the Persistent Storage due to file system errors.")
740 self.on_tps_activation_failed(label)
742 # Ask the user if they want to repair the filesystem
743 dialog = MessageDialog(
744 message_type=Gtk.MessageType.WARNING,
745 title=_("File System Errors"),
746 text=_(
747 """Errors were detected in the Persistent Storage file system.
749 Tails can try to fix these errors, but this may take a long time, not all data may be recoverable, and it might make it harder to further recover your data.
751 If you already have an up-to-date backup of your Persistent Storage, we recommend that you try to repair.
753 If you don't have a backup, we recommend that you create a backup first."""
755 cancel_label=_("Cancel"),
756 ok_label=_("Repair File System"),
757 third_button_label=_("Create Backup"),
758 destructive=True,
760 dialog.set_transient_for(self)
761 response = dialog.run()
762 dialog.destroy()
764 # The REJECT response is not used by GTK by default, so we use
765 # it for the "Create Backup" button out of better options.
766 if response == Gtk.ResponseType.REJECT:
767 label = _(
768 "Start Tails to learn how to create a backup of your Persistent Storage."
770 self.on_tps_activation_failed(label)
771 # XXX: Actually open some documentation on how to create a backup
772 # using ddrescue after Tails has started
773 return
775 if response == Gtk.ResponseType.OK:
776 self.repair_tps_filesystem()
778 def on_tps_upgrade_failed(self):
779 label = _(
780 "Failed to upgrade the Persistent Storage. "
781 "Please start Tails and send an error report."
783 self.on_tps_activation_failed(label)
785 def on_tps_unlock_failed(self):
786 label = _(
787 "Failed to unlock the Persistent Storage. "
788 "Please start Tails and send an error report."
790 self.on_tps_activation_failed(label)
792 def on_tps_activation_failed(self, label=None):
793 if not label:
794 label = _(
795 "Failed to activate the Persistent Storage. "
796 "Please start Tails and send an error report."
798 self.image_storage_state.set_visible(True)
799 self.spinner_storage_unlock.set_visible(False)
800 self.label_storage_unlock_status.set_label(label)
801 self.image_storage_unlock_failed.set_visible(True)
802 self.button_start.set_sensitive(True)
803 self.box_storage_unlock_status.set_visible(True)
805 def on_tps_upgrading(self):
806 label = _("Upgrading the Persistent Storage. This may take a while…")
807 self.label_storage_unlock_status.set_label(label)
810 class GreeterBackgroundWindow(Gtk.ApplicationWindow):
811 def __init__(self, app):
812 super().__init__(app)
813 Gtk.Window.__init__(
814 self, title=_(tailsgreeter.APPLICATION_TITLE), application=app
816 self.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 1))