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/>
19 from typing
import TYPE_CHECKING
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
,
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
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"
61 PREFERRED_HEIGHT
= 470
64 class GreeterMainWindow(Gtk
.Window
, TranslatableWindow
):
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
)
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(),
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
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
)
168 self
.confirm_dialog
= MessageDialog(
169 message_type
=Gtk
.MessageType
.WARNING
,
170 title
=_("Persistent Storage Still Locked"),
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
)
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
)
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
)
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
]:
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
]
271 def apply_settings(self
):
272 for setting
in self
.settings
:
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
)):
283 changed
= setting
.load()
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
297 settings_loaded
= True
298 except SettingNotFoundError
as e
:
300 # The settings file does not exist, so we create it by
301 # applying the setting's default value.
304 for setting
in self
.settings
.additional_settings
:
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.
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
320 self
.add_setting(setting
.id)
321 except SettingNotFoundError
as e
:
323 # The settings file does not exist, so we create it by
324 # applying the setting's default value.
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_
)
342 old_details
= self
.dialog_add_setting
.stack
.get_child_by_name(
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_
]
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")
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()
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):
392 # First, upgrade the storage if needed (skip if we're
393 # doing a forceful fsck, because then we already tried
395 if not self
.persistence_setting
.is_upgraded
and not forceful_fsck
:
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
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
:
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
)
418 GLib
.idle_add(self
.cb_unlock_failed_with_filesystem_errors
)
419 except PersistentStorageError
as e
:
421 GLib
.idle_add(self
.on_tps_unlock_failed
)
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()
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)
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"),
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."
470 dialog_
.set_transient_for(self
)
474 def do_repair_tps_filesystem():
476 self
.persistence_setting
.repair_filesystem()
477 GLib
.idle_add(on_tps_repair_success
)
478 except PersistentStorageError
as 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()
492 if response
== Gtk
.ResponseType
.OK
:
493 self
.unlock_tps(forceful_fsck
=True)
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":
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
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
)
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)
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():
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)
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()
560 def cb_button_start_clicked(self
, widget
, user_data
=None):
561 # Ask for confirmation when Persistent Storage exists but is not
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
:
577 def cb_button_storage_unlock_clicked(self
, widget
, user_data
=None):
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():
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
)
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)
607 def cb_infobar_response(self
, infobar
, response_id
, user_data
=None):
608 infobar
.set_visible(False)
611 def cb_listbox_add_setting_focus(self
, widget
, direction
, user_data
=None):
612 self
.dialog_add_setting
.listbox_focus()
615 def cb_listbox_add_setting_row_activated(self
, listbox
, row
, user_data
=None):
616 self
.dialog_add_setting
.listbox_row_activated(row
)
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
)
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
:
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
)
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
:
651 def cb_toolbutton_settings_add_clicked(self
, user_data
=None):
652 self
.run_add_setting_dialog()
655 def cb_toolbutton_settings_mnemonic_activate(self
, widget
, group_cycling
):
656 self
.run_add_setting_dialog()
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
664 def cb_tps_unlocked(self
):
665 logging
.debug("Storage unlocked")
667 # Activate the Persistent Storage
669 self
.persistence_setting
.activate_persistent_storage()
670 except FeatureActivationFailedError
as e
:
675 "Start Tails and open the Persistent Storage settings to find out more."
678 self
.on_tps_activation_failed(label
)
680 except PersistentStorageError
as e
:
682 self
.on_tps_activation_failed()
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()
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"),
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"),
760 dialog
.set_transient_for(self
)
761 response
= dialog
.run()
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
:
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
775 if response
== Gtk
.ResponseType
.OK
:
776 self
.repair_tps_filesystem()
778 def on_tps_upgrade_failed(self
):
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
):
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):
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
)
814 self
, title
=_(tailsgreeter
.APPLICATION_TITLE
), application
=app
816 self
.override_background_color(Gtk
.StateFlags
.NORMAL
, Gdk
.RGBA(0, 0, 0, 1))