7#include <unordered_set>
15#include <glibmm/ustring.h>
22 const auto total_seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed).count();
23 const auto hours = total_seconds / 3600;
24 const auto minutes = (total_seconds % 3600) / 60;
25 const auto seconds = total_seconds % 60;
27 std::ostringstream oss;
28 oss << std::setfill(
'0');
29 if (hours > 0) oss << std::setw(2) << hours <<
':';
30 oss << std::setw(2) << minutes <<
':' << std::setw(2) << seconds;
35 const std::string& svg_path,
36 const Glib::ustring& text) {
37 auto* box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
38 box->set_halign(Gtk::Align::START);
40 bool icon_loaded =
false;
42 auto texture = Gdk::Texture::create_from_filename(svg_path);
43 auto* image = Gtk::make_managed<Gtk::Image>();
45 image->set_pixel_size(20);
46 image->set_halign(Gtk::Align::START);
49 }
catch (
const Glib::Error&) {
54 auto* fallback = Gtk::make_managed<Gtk::Label>(
"•");
55 fallback->set_halign(Gtk::Align::START);
56 fallback->add_css_class(
"small-label");
57 box->append(*fallback);
60 auto* label = Gtk::make_managed<Gtk::Label>(text);
61 label->set_halign(Gtk::Align::START);
62 label->set_hexpand(
true);
63 label->set_xalign(0.0f);
66 button.set_child(*box);
70 std::ifstream input(path, std::ios::binary);
71 if (!input.is_open())
return {};
73 std::ostringstream content;
74 content << input.rdbuf();
87 "BastionGuard™ Recovery Data\n"
89 "Copyright (C) 2025–2026 Calogero Scarnà.\n"
91 "This program is free software: you can redistribute it and/or modify "
92 "it under the terms of the GNU General Public License as published by "
93 "the Free Software Foundation, either version 3 of the License, or "
94 "(at your option) any later version.\n"
96 "See the LICENSE file for the complete GNU GPLv3 text.\n"
97 "BastionGuard™ is a trademark of Calogero Scarnà.\n";
106: brand_label_(
"BastionGuard™"),
107btn_dashboard_(
tr(
"Dashboard")),
108btn_recovery_(
tr(
"Recovery wizard")),
109btn_about_(
tr(
"About")),
110btn_refresh_(
tr(
"Refresh devices")),
112btn_theme_light_(
"☀"),
113recovery_title_(
tr(
"Recovery wizard")),
114recovery_subtitle_(
tr(
"Choose a source, set a destination, scan read-only, then export recovered data.")),
115btn_wizard_back_(
tr(
"Back")),
116btn_wizard_next_(
tr(
"Next")),
117source_step_label_(
tr(
"Select a physical disk or image file. Physical disks require read access; use the pkexec button when needed.")),
118btn_open_image_(
tr(
"Open image")),
119btn_use_selected_device_(
tr(
"Use selected disk")),
120btn_launch_elevated_(
tr(
"Relaunch with pkexec")),
121destination_step_label_(
tr(
"Choose a destination folder on a different disk or mount point than the source.")),
122btn_choose_destination_(
tr(
"Choose destination")),
123scan_step_label_(
tr(
"Run a read-only scan before exporting recovered data.")),
124btn_scan_now_(
tr(
"Start scan")),
125recover_step_label_(
tr(
"Export detected volumes, filesystem-level recoveries, and carved files.")),
126btn_results_select_all_(
tr(
"Select all")),
127btn_results_clear_(
tr(
"Clear selection")),
128btn_recover_now_(
tr(
"Start recovery")) {
131 set_default_size(800, 600);
132 set_size_request(1100, 700);
137 build_recovery_page();
140 page_stack_.add(dashboard_page_,
"dashboard",
tr(
"Dashboard"));
141 page_stack_.add(recovery_page_,
"recovery",
tr(
"Recovery wizard"));
142 page_stack_.add(about_page_,
"about",
tr(
"About"));
143 page_stack_.set_visible_child(dashboard_page_);
144 page_stack_.set_hexpand(
true);
145 page_stack_.set_vexpand(
true);
146 page_stack_.set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
147 page_stack_.set_transition_duration(220);
148 page_stack_.add_css_class(
"content");
150 main_box_.append(sidebar_);
151 main_box_.append(page_stack_);
152 set_child(main_box_);
154 log_buffer_ = Gtk::TextBuffer::create();
155 log_view_.set_buffer(log_buffer_);
156 log_view_.set_editable(
false);
157 log_view_.set_monospace(
true);
159 recovery_devices_list_.set_selection_mode(Gtk::SelectionMode::SINGLE);
160 devices_list_.set_selection_mode(Gtk::SelectionMode::SINGLE);
163 const std::string saved_theme = read_theme();
164 current_theme_ = (saved_theme ==
"light") ? ThemeMode::Light : ThemeMode::Dark;
166 apply_theme(current_theme_);
169 activate_sidebar_button(dashboard_button_);
170 update_source_state();
171 update_wizard_step();
187 btn_results_select_all_.signal_clicked().connect([
this]() { set_all_results_selected(
true); });
188 btn_results_clear_.signal_clicked().connect([
this]() { set_all_results_selected(
false); });
197 auto* header = Gtk::make_managed<Gtk::HeaderBar>();
198 header->set_show_title_buttons(
false);
199 header->set_decoration_layout(
"");
200 header->add_css_class(
"custom-headerbar");
202 auto* header_left = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
203 header_left->set_hexpand(
false);
204 header_left->add_css_class(
"header-left");
205 header->pack_start(*header_left);
207 auto* spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
208 spacer->set_hexpand(
true);
209 header->pack_start(*spacer);
211 auto* header_right = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
212 header_right->add_css_class(
"header-right");
215 auto pix_min = Gdk::Pixbuf::create_from_file(
resource(
"icon-minimize.png"), 24, 24,
true);
216 auto* btn_min = Gtk::make_managed<Gtk::Button>();
217 btn_min->set_child(*Gtk::make_managed<Gtk::Image>(pix_min));
218 btn_min->add_css_class(
"header-button");
219 btn_min->set_tooltip_text(
tr(
"Minimize"));
221 header_right->append(*btn_min);
222 }
catch (
const Glib::Error& ex) {
223 std::cerr <<
"Errore caricamento icon-minimize.png: " << ex.what() << std::endl;
225 auto* btn_min_fallback = Gtk::make_managed<Gtk::Button>(
"—");
226 btn_min_fallback->add_css_class(
"header-button");
227 btn_min_fallback->set_tooltip_text(
tr(
"Minimize"));
229 header_right->append(*btn_min_fallback);
233 auto pix_close = Gdk::Pixbuf::create_from_file(
resource(
"icon-close.png"), 24, 24,
true);
234 auto* btn_close = Gtk::make_managed<Gtk::Button>();
235 btn_close->set_child(*Gtk::make_managed<Gtk::Image>(pix_close));
236 btn_close->add_css_class(
"header-button");
237 btn_close->set_tooltip_text(
tr(
"Close"));
239 header_right->append(*btn_close);
240 }
catch (
const Glib::Error& ex) {
241 std::cerr <<
"Errore caricamento icon-close.png: " << ex.what() << std::endl;
243 auto* btn_close_fallback = Gtk::make_managed<Gtk::Button>(
"✕");
244 btn_close_fallback->add_css_class(
"header-button");
245 btn_close_fallback->set_tooltip_text(
tr(
"Close"));
247 header_right->append(*btn_close_fallback);
250 header->pack_end(*header_right);
251 set_titlebar(*header);
258 auto* hdr = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 8);
259 hdr->add_css_class(
"sidebar-header");
261 auto* logo_img = Gtk::make_managed<Gtk::Image>();
263 auto texture = Gdk::Texture::create_from_filename(
resource(
"logo-sidebar.png"));
264 logo_img->set(texture);
265 logo_img->set_pixel_size(48);
267 logo_img->set_halign(Gtk::Align::START);
268 logo_img->set_margin_bottom(4);
269 hdr->append(*logo_img);
305 auto* sep1 = Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL);
306 sep1->add_css_class(
"sidebar-separator");
309 auto* theme_title = Gtk::make_managed<Gtk::Label>(
tr(
"Theme"));
310 theme_title->set_halign(Gtk::Align::START);
311 theme_title->add_css_class(
"sidebar-group-label");
314 auto* theme_row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
315 theme_row->add_css_class(
"sidebar-theme-row");
316 theme_row->set_halign(Gtk::Align::FILL);
317 theme_row->set_hexpand(
true);
331 auto* sep2 = Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL);
332 sep2->add_css_class(
"sidebar-separator");
335 auto* lang_title = Gtk::make_managed<Gtk::Label>(
tr(
"Language"));
336 lang_title->set_halign(Gtk::Align::START);
337 lang_title->add_css_class(
"sidebar-group-label");
343 auto lang_model = Gtk::StringList::create({
362 else if (saved ==
"de")
lang_combo_.set_selected(2);
363 else if (saved ==
"es")
lang_combo_.set_selected(3);
364 else if (saved ==
"fr")
lang_combo_.set_selected(4);
365 else if (saved ==
"ja")
lang_combo_.set_selected(5);
366 else if (saved ==
"nl")
lang_combo_.set_selected(6);
367 else if (saved ==
"pl")
lang_combo_.set_selected(7);
368 else if (saved ==
"pt")
lang_combo_.set_selected(8);
369 else if (saved ==
"ru")
lang_combo_.set_selected(9);
370 else if (saved ==
"ar")
lang_combo_.set_selected(10);
374 lang_combo_.property_selected().signal_changed().connect(
379 auto* spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
380 spacer->set_vexpand(
true);
385 auto* info = Gtk::make_managed<Gtk::Label>(
tr(
"Read-only forensic mode"));
386 info->set_halign(Gtk::Align::START);
387 info->add_css_class(
"small-label");
440 devices_scroller_.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
483 wizard_notice_label_.set_text(
tr(
"Avviso: non si garantisce il recupero totale dei file. La quantità e l'integrità dei dati recuperati dipendono da danni al filesystem, sovrascritture, frammentazione, cifratura, compressione e stato del dispositivo."));
521 results_scroller_.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
529 auto* results_inner = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
530 results_inner->set_margin_top(10);
531 results_inner->set_margin_bottom(10);
532 results_inner->set_margin_start(10);
533 results_inner->set_margin_end(10);
558 auto* spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
559 spacer->set_hexpand(
true);
598 const std::string css_path =
609 }
catch (
const Glib::Error& ex) {
610 std::cerr <<
"Errore caricando CSS '" << css_path <<
"': " << ex.what() << std::endl;
614 auto display = Gdk::Display::get_default();
616 gtk_style_context_add_provider_for_display(
619 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
637 for (
auto* b :
sidebar_buttons_) b->remove_css_class(
"active-sidebar-button");
638 if (button) button->add_css_class(
"active-sidebar-button");
645 std::ostringstream ss;
646 ss <<
trs(
"Detected devices: ") <<
devices_.size();
654 int selected_index = -1;
656 for (
size_t i = 0; i <
devices_.size(); ++i) {
659 auto make_row = [&](
bool include_path_first) -> Gtk::ListBoxRow* {
660 auto* row_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
661 row_box->set_margin_top(10);
662 row_box->set_margin_bottom(10);
663 row_box->set_margin_start(12);
664 row_box->set_margin_end(12);
666 std::ostringstream title;
667 if (include_path_first)
668 title << d.path <<
" • " <<
human_size(d.size_bytes);
670 title << d.name <<
" • " <<
human_size(d.size_bytes);
672 std::ostringstream subtitle;
673 subtitle << (d.model.empty() ?
tr(
"Disk") : d.model) <<
" • "
674 << (d.transport.empty() ?
tr(
"Block") : d.transport);
676 if (d.removable) subtitle <<
trs(
" • removable");
678 subtitle <<
trs(
" • needs elevation");
680 auto* lbl1 = Gtk::make_managed<Gtk::Label>(title.str());
681 auto* lbl2 = Gtk::make_managed<Gtk::Label>(subtitle.str());
682 lbl1->set_halign(Gtk::Align::START);
683 lbl2->set_halign(Gtk::Align::START);
684 lbl1->set_xalign(0.0f);
685 lbl2->set_xalign(0.0f);
686 lbl1->add_css_class(
"subtitle");
687 lbl2->add_css_class(
"small-label");
688 row_box->append(*lbl1);
689 row_box->append(*lbl2);
691 auto* row = Gtk::make_managed<Gtk::ListBoxRow>();
692 row->set_child(*row_box);
700 selected_index =
static_cast<int>(i);
707 if (selected_index >= 0) {
708 if (
auto* row =
devices_list_.get_row_at_index(selected_index))
720 int selected_index = -1;
721 for (
size_t i = 0; i <
devices_.size(); ++i) {
723 selected_index =
static_cast<int>(i);
731 if (selected_index >= 0) {
732 if (
auto* dashboard_row =
devices_list_.get_row_at_index(selected_index))
746 scan_step_label_.set_text(
tr(
"Run a read-only scan before exporting recovered data. Non si garantisce il recupero totale dei file."));
747 recover_step_label_.set_text(
tr(
"Export detected volumes, filesystem-level recoveries, and carved files. Non si garantisce il recupero totale dei file."));
752 std::ostringstream ss;
756 ss <<
trs(
"\nAccess: needs elevation");
758 ss <<
trs(
"\nAccess: ready");
762 std::ostringstream scan_msg;
764 <<
trs(
"\n\nRun a read-only scan before continuing.");
767 std::ostringstream rec_msg;
768 rec_msg <<
trs(
"Destination: ")
771 <<
trs(
"\n\nBrowse the recovery tree, keep all items selected or choose only the branches you want, then click \"Start recovery\".");
806 page_stack_.set_transition_type(Gtk::StackTransitionType::SLIDE_RIGHT);
812 page_stack_.set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT);
826 file_dialog_->open(*
this, [
this](
const Glib::RefPtr<Gio::AsyncResult>& result) {
832 const auto path = file->get_path();
836 }
catch (
const Glib::Error&) {
848 folder_dialog_->select_folder(*
this, [
this](
const Glib::RefPtr<Gio::AsyncResult>& result) {
860 }
catch (
const Glib::Error&) {
873 const int idx = row->get_index();
874 if (idx < 0 ||
static_cast<size_t>(idx) >=
devices_.size())
return;
876 const auto& d =
devices_[
static_cast<size_t>(idx)];
884 const int idx = row->get_index();
885 if (idx < 0 ||
static_cast<size_t>(idx) >=
devices_.size())
return;
887 const auto& d =
devices_[
static_cast<size_t>(idx)];
952 append_log(
trs(
"Source access denied. Relaunch with pkexec or adjust permissions."));
969 tr(
"Elapsed: ") + format_elapsed(std::chrono::steady_clock::now() -
scan_started_at_));
976 Glib::signal_idle().connect_once([
this, report]() {
on_scan_completed(report); });
979 Glib::signal_idle().connect_once([
this, progress]() {
on_scan_progress(progress); });
1001 if (selected.empty()) {
1007 append_log(
trs(
"Selected items for recovery: ") + std::to_string(selected.size()));
1019 append_log(
trs(
"Already running with elevated privileges."));
1034 double fraction = progress.
fraction;
1035 if (fraction < 0.0) fraction = 0.0;
1036 if (fraction > 1.0) fraction = 1.0;
1040 std::ostringstream pct;
1041 pct << static_cast<int>(fraction * 100.0) <<
'%';
1044 std::string status = progress.
stage;
1045 if (!progress.
detail.empty()) {
1046 if (!status.empty()) status +=
" • ";
1047 status += progress.
detail;
1062 tr(
"Elapsed: ") + format_elapsed(std::chrono::steady_clock::now() -
scan_started_at_)
1067 for (
const auto& line : report.
logs)
1070 std::ostringstream ss;
1071 ss <<
trs(
"Scan completed. Results: ") << report.
results.size();
1086 if (result.
type.find(
"NTFS") != std::string::npos)
return "NTFS";
1087 if (result.
type.find(
"ext4") != std::string::npos || result.
type.find(
"EXT4") != std::string::npos)
return "ext4";
1088 if (result.
type.find(
"Btrfs") != std::string::npos || result.
type.find(
"BTRFS") != std::string::npos)
return "Btrfs";
1089 if (result.
type.find(
"exFAT") != std::string::npos)
return "exFAT";
1090 if (result.
type.find(
"FAT32") != std::string::npos)
return "FAT32";
1092 return "Other detected items";
1096 switch (result.
kind) {
1105 if (result.
type.find(
"$MFT") != std::string::npos)
return "Metadata";
1116 std::map<std::string, std::vector<size_t>> subgroups;
1119 for (
size_t report_index = 0; report_index < report.
results.size(); ++report_index) {
1120 const auto& r = report.
results[report_index];
1132 auto* root_expander = Gtk::make_managed<Gtk::Expander>();
1133 root_expander->set_label(scan_root_name +
" (" + std::to_string(count) +
")");
1134 root_expander->set_expanded(
true);
1136 auto* root_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
1137 root_box->set_margin_top(6);
1138 root_box->set_margin_bottom(6);
1139 root_box->set_margin_start(6);
1140 root_box->set_margin_end(6);
1143 auto* empty = Gtk::make_managed<Gtk::Label>(
trs(
"No recoverable items found in this scan result."));
1144 empty->set_halign(Gtk::Align::START);
1145 empty->set_xalign(0.0f);
1146 empty->add_css_class(
"small-label");
1147 root_box->append(*empty);
1150 for (
const auto& subgroup_entry : subgroups) {
1151 const auto& subgroup_name = subgroup_entry.first;
1152 const auto& indices = subgroup_entry.second;
1154 auto* sub_expander = Gtk::make_managed<Gtk::Expander>();
1155 sub_expander->set_label(subgroup_name +
" (" + std::to_string(indices.size()) +
")");
1156 sub_expander->set_expanded(
true);
1158 auto* subgroup_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
1159 subgroup_box->set_margin_top(4);
1160 subgroup_box->set_margin_bottom(4);
1161 subgroup_box->set_margin_start(8);
1162 subgroup_box->set_margin_end(4);
1164 for (
size_t report_index : indices) {
1165 const auto& r = report.
results[report_index];
1167 auto* row_layout = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
1168 row_layout->set_margin_top(6);
1169 row_layout->set_margin_bottom(6);
1170 row_layout->set_margin_start(10);
1171 row_layout->set_margin_end(10);
1173 auto* check = Gtk::make_managed<Gtk::CheckButton>();
1174 check->set_active(
true);
1175 check->set_valign(Gtk::Align::START);
1176 check->signal_toggled().connect([
this, report_index, check]() {
1183 row_layout->append(*check);
1185 auto* row_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 3);
1186 row_box->set_hexpand(
true);
1188 std::ostringstream title;
1190 if (r.size_bytes > 0)
1193 std::ostringstream sub;
1194 sub <<
trs(
"Offset: ") << r.offset_bytes
1195 <<
" (0x" << std::hex << std::uppercase << r.offset_bytes << std::dec <<
")";
1196 if (r.confidence > 0.0) {
1197 std::ostringstream conf;
1198 conf << std::fixed << std::setprecision(0) << (r.confidence * 100.0) <<
"%";
1199 sub <<
trs(
" • Confidence: ") << conf.str();
1201 if (!r.source.empty())
1202 sub <<
" • " << r.source;
1204 auto* lbl_title = Gtk::make_managed<Gtk::Label>(title.str());
1205 lbl_title->set_halign(Gtk::Align::START);
1206 lbl_title->set_xalign(0.0f);
1207 lbl_title->add_css_class(
"subtitle");
1209 auto* lbl_sub = Gtk::make_managed<Gtk::Label>(sub.str());
1210 lbl_sub->set_halign(Gtk::Align::START);
1211 lbl_sub->set_xalign(0.0f);
1212 lbl_sub->add_css_class(
"small-label");
1213 lbl_sub->set_ellipsize(Pango::EllipsizeMode::END);
1215 row_box->append(*lbl_title);
1216 row_box->append(*lbl_sub);
1218 if (!r.detail.empty()) {
1219 auto* lbl_detail = Gtk::make_managed<Gtk::Label>(r.detail);
1220 lbl_detail->set_halign(Gtk::Align::START);
1221 lbl_detail->set_xalign(0.0f);
1222 lbl_detail->add_css_class(
"small-label");
1223 lbl_detail->set_wrap(
true);
1224 lbl_detail->set_wrap_mode(Pango::WrapMode::WORD_CHAR);
1225 row_box->append(*lbl_detail);
1228 row_layout->append(*row_box);
1229 subgroup_box->append(*row_layout);
1232 sub_expander->set_child(*subgroup_box);
1233 root_box->append(*sub_expander);
1236 root_expander->set_child(*root_box);
1239 std::ostringstream cnt;
1241 cnt <<
trs(
"No recoverable items found.");
1242 }
else if (count == 1) {
1243 cnt << count <<
trs(
" recoverable item detected in this scan.");
1245 cnt << count <<
trs(
" recoverable items detected in this scan.");
1249 std::ostringstream rec_msg;
1250 rec_msg <<
trs(
"Destination: ")
1253 <<
trs(
"Scan result: ") << scan_root_name
1255 <<
trs(
"This tree belongs to the current scan result. Select all items or only the entries you want to recover, then click \"Start recovery\".");
1268 std::ostringstream ss;
1269 ss <<
trs(
"Selected items: ") << selected;
1270 if (total > 0) ss <<
" / " << total;
1276 std::vector<size_t> out;
1314 #if GTKMM_CHECK_VERSION(4,10,0)
1336 auto* logo_img = Gtk::make_managed<Gtk::Image>();
1338 auto texture = Gdk::Texture::create_from_filename(
resource(
"logo.svg"));
1339 logo_img->set(texture);
1340 logo_img->set_pixel_size(192);
1342 logo_img->set_margin_bottom(12);
1346 auto* name_label = Gtk::make_managed<Gtk::Label>(
tr(
"BastionGuard™ Recovery Data"));
1347 name_label->add_css_class(
"page-title");
1348 name_label->set_margin_bottom(4);
1351 std::string version_str =
"unknown";
1353 const std::string version_path =
resource(
"version.bs");
1354 std::ifstream vf(version_path);
1356 std::getline(vf, version_str);
1357 version_str.erase(version_str.find_last_not_of(
" \t\r\n") + 1);
1358 version_str.erase(0, version_str.find_first_not_of(
" \t\r\n"));
1362 auto* version_label = Gtk::make_managed<Gtk::Label>(
trs(
"Version ") + version_str);
1363 version_label->add_css_class(
"small-label");
1364 version_label->set_margin_bottom(18);
1367 auto* about_notebook = Gtk::make_managed<Gtk::Notebook>();
1368 about_notebook->set_hexpand(
true);
1369 about_notebook->set_vexpand(
true);
1370 about_notebook->set_halign(Gtk::Align::FILL);
1371 about_notebook->set_valign(Gtk::Align::FILL);
1372 about_notebook->set_size_request(680, 360);
1374 auto* overview_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 18);
1375 overview_box->set_margin_top(24);
1376 overview_box->set_margin_bottom(24);
1377 overview_box->set_margin_start(24);
1378 overview_box->set_margin_end(24);
1380 auto* desc_label = Gtk::make_managed<Gtk::Label>(
1381 tr(
"A read-only forensic recovery tool supporting ext4, btrfs, NTFS, FAT32,exFAT, XFS and HFS+.\nScan physical disks or image files without writing a single byte to the source."));
1382 desc_label->set_wrap(
true);
1383 desc_label->set_justify(Gtk::Justification::CENTER);
1384 overview_box->append(*desc_label);
1386 auto* fs_label = Gtk::make_managed<Gtk::Label>(
1387 tr(
"Supported filesystems: ext4 • Btrfs • NTFS • FAT32 • exFAT • XFS • HFS+"));
1388 fs_label->add_css_class(
"subtitle");
1389 fs_label->set_wrap(
true);
1390 fs_label->set_justify(Gtk::Justification::CENTER);
1391 overview_box->append(*fs_label);
1393 auto* license_summary = Gtk::make_managed<Gtk::Label>(
1394 tr(
"Released under GNU GPLv3 or later. BastionGuard™ is a trademark of Calogero Scarnà."));
1395 license_summary->add_css_class(
"small-label");
1396 license_summary->set_justify(Gtk::Justification::CENTER);
1397 license_summary->set_wrap(
true);
1398 overview_box->append(*license_summary);
1400 auto* overview_tab = Gtk::make_managed<Gtk::Label>(
tr(
"Overview"));
1401 about_notebook->append_page(*overview_box, *overview_tab);
1403 auto* license_scroller = Gtk::make_managed<Gtk::ScrolledWindow>();
1404 license_scroller->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
1405 license_scroller->set_min_content_width(640);
1406 license_scroller->set_min_content_height(300);
1408 auto* license_text = Gtk::make_managed<Gtk::TextView>();
1409 license_text->set_editable(
false);
1410 license_text->set_cursor_visible(
false);
1411 license_text->set_wrap_mode(Gtk::WrapMode::WORD);
1412 license_text->set_left_margin(14);
1413 license_text->set_right_margin(14);
1414 license_text->set_top_margin(14);
1415 license_text->set_bottom_margin(14);
1417 auto license_buffer = Gtk::TextBuffer::create();
1418 license_buffer->set_text(load_license_text());
1419 license_text->set_buffer(license_buffer);
1420 license_scroller->set_child(*license_text);
1422 auto* license_tab = Gtk::make_managed<Gtk::Label>(
tr(
"License"));
1423 about_notebook->append_page(*license_scroller, *license_tab);
1436 page_stack_.set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT);
1442 const std::string path =
1443 Glib::get_home_dir() +
"/.config/bastionguard-recovery-data/lang.conf";
1445 std::ifstream f(path);
1446 if (!f.is_open())
return "en";
1451 static const std::unordered_set<std::string> supported = {
1452 "en",
"it",
"de",
"es",
"fr",
"ja",
"nl",
"pl",
"pt",
"ru",
"ar"
1455 return supported.count(val) ? val :
"en";
1459 const std::string dir =
1460 Glib::get_home_dir() +
"/.config/bastionguard-recovery-data";
1462 std::filesystem::create_directories(dir);
1463 std::ofstream f(dir +
"/lang.conf");
1464 if (f.is_open()) f << lang_code <<
"\n";
1468 const std::string path =
1469 Glib::get_home_dir() +
"/.config/bastionguard-recovery-data/theme.conf";
1471 std::ifstream f(path);
1472 if (!f.is_open())
return "dark";
1477 if (val ==
"dark" || val ==
"light")
1484 const std::string dir =
1485 Glib::get_home_dir() +
"/.config/bastionguard-recovery-data";
1487 std::filesystem::create_directories(dir);
1488 std::ofstream f(dir +
"/theme.conf");
1489 if (f.is_open()) f << theme_code <<
"\n";
1498 std::string new_lang =
"en";
1500 case 1: new_lang =
"it";
break;
1501 case 2: new_lang =
"de";
break;
1502 case 3: new_lang =
"es";
break;
1503 case 4: new_lang =
"fr";
break;
1504 case 5: new_lang =
"ja";
break;
1505 case 6: new_lang =
"nl";
break;
1506 case 7: new_lang =
"pl";
break;
1507 case 8: new_lang =
"pt";
break;
1508 case 9: new_lang =
"ru";
break;
1509 case 10: new_lang =
"ar";
break;
1510 default: new_lang =
"en";
break;
1513 if (new_lang == current)
return;
1517 auto alert = Gtk::AlertDialog::create(
tr(
"Restart required"));
1519 tr(
"The language will change after restarting the application.\nRestart now?"));
1520 alert->set_buttons({
tr(
"Cancel"),
tr(
"Restart")});
1521 alert->set_default_button(1);
1522 alert->set_cancel_button(0);
1524 alert->choose(*
this, [
this, alert, current](Glib::RefPtr<Gio::AsyncResult>& result) {
1527 btn = alert->choose_finish(result);
1531 char self_path[4096] = {};
1532 ssize_t len = readlink(
"/proc/self/exe", self_path,
sizeof(self_path) - 1);
1534 self_path[len] =
'\0';
1535 char* argv[] = {self_path,
nullptr};
1536 execv(self_path, argv);
1538 get_application()->quit();
1544 else if (current ==
"de")
lang_combo_.set_selected(2);
1545 else if (current ==
"es")
lang_combo_.set_selected(3);
1546 else if (current ==
"fr")
lang_combo_.set_selected(4);
1547 else if (current ==
"ja")
lang_combo_.set_selected(5);
1548 else if (current ==
"nl")
lang_combo_.set_selected(6);
1549 else if (current ==
"pl")
lang_combo_.set_selected(7);
1550 else if (current ==
"pt")
lang_combo_.set_selected(8);
1551 else if (current ==
"ru")
lang_combo_.set_selected(9);
1552 else if (current ==
"ar")
lang_combo_.set_selected(10);
1561 const char* units[] = {
"B",
"KiB",
"MiB",
"GiB",
"TiB"};
1562 double value =
static_cast<double>(bytes);
1565 while (value >= 1024.0 && unit_index < 4) {
1570 std::ostringstream oss;
1571 oss << std::fixed << std::setprecision(2) << value <<
' ' << units[unit_index];
static std::vector< DeviceInfo > enumerate_linux_block_devices(bool include_health_probe=false)
void on_sidebar_recovery()
Gtk::Button btn_wizard_back_
void update_selected_results_state()
void on_recovery_device_activated(Gtk::ListBoxRow *row)
Gtk::DropDown lang_combo_
Gtk::Button btn_open_image_
void populate_device_lists()
static void save_theme(const std::string &theme_code)
Gtk::ScrolledWindow devices_scroller_
Gtk::Label wizard_notice_label_
Gtk::Button btn_choose_destination_
Gtk::Label results_selected_label_
std::string selected_source_label_
void populate_scan_results(const ScanReport &report)
Gtk::ListBox devices_list_
void on_scan_completed(const ScanReport &report)
Gtk::Button btn_recover_now_
Glib::RefPtr< Gtk::CssProvider > css_provider_
Gtk::Button btn_launch_elevated_
Gtk::Button btn_use_selected_device_
static std::string result_subgroup_label(const ScanResult &result)
Gtk::ScrolledWindow log_scroller_
Gtk::ListBox recovery_devices_list_
void append_log(const std::string &line)
void update_theme_buttons()
Gtk::Label source_card_label_
Glib::RefPtr< Gtk::FileDialog > folder_dialog_
Gtk::Box destination_step_box_
Gtk::Button * recovery_button_
Gtk::Button btn_scan_now_
Gtk::Button * dashboard_button_
DashboardPage dashboard_page_
Gtk::Button * about_button_
std::vector< Gtk::CheckButton * > result_check_buttons_
void update_wizard_step()
~MainWindow() override=default
sigc::connection scan_timer_connection_
RecoveryWorker recovery_worker_
static std::string human_size(uint64_t bytes)
Gtk::Frame devices_frame_
static std::string result_group_label(const ScanResult &result)
Gtk::Button btn_dashboard_
static void save_language(const std::string &lang_code)
void on_sidebar_dashboard()
void on_choose_destination()
Gtk::Box results_tree_box_
Gtk::Label scan_timer_label_
void select_source(const std::string &path, const std::string &label)
void on_language_changed()
Gtk::Button btn_results_clear_
Gtk::Box recover_step_box_
Gtk::Button btn_theme_light_
std::vector< size_t > selected_result_indices() const
void on_dashboard_device_activated(Gtk::ListBoxRow *row)
void on_scan_progress(const ScanProgress &progress)
Gtk::Button btn_recovery_
Gtk::Label status_card_label_
Gtk::ProgressBar scan_progress_bar_
std::vector< DeviceInfo > devices_
std::vector< Gtk::Button * > sidebar_buttons_
Gtk::Box results_actions_row_
void on_refresh_devices()
Gtk::Label results_count_label_
Gtk::Frame results_frame_
Gtk::ScrolledWindow results_scroller_
Gtk::Button btn_theme_dark_
ScanProgress last_scan_progress_
void apply_theme(bool use_dark)
Gtk::Box source_step_box_
std::vector< bool > result_selection_
void activate_sidebar_button(Gtk::Button *btn)
Gtk::Label recover_step_label_
Glib::RefPtr< Gtk::FileDialog > file_dialog_
Gtk::Label scan_status_label_
Gtk::Button btn_results_select_all_
Gtk::Label destination_step_label_
static std::string read_theme()
Glib::RefPtr< Gtk::TextBuffer > log_buffer_
std::chrono::steady_clock::time_point scan_started_at_
Gtk::Label update_card_label_
void on_recovery_completed(const RecoveryResult &result)
Gtk::ScrolledWindow recovery_devices_scroller_
Gtk::Box destination_button_row_
void update_action_controls()
void on_launch_elevated()
Gtk::Label source_step_label_
void build_recovery_page()
Gtk::Box source_button_row_
Gtk::Button btn_wizard_next_
Gtk::Label recovery_title_
std::string selected_source_path_
Gtk::Box recovery_header_
void update_source_state()
Gtk::Label recovery_subtitle_
static std::string read_language()
void on_use_selected_device()
Gtk::Label scan_step_label_
void set_all_results_selected(bool selected)
static bool is_running_as_root()
static bool launch_pkexec_relaunch(std::string &error, const std::string &extra_args="")
static bool can_read_source(const std::string &path)
void start(const ScanReport &report, const std::string &output_dir, const std::vector< size_t > &selected_result_indices, CompletionCallback cb)
void start(const std::string &path, CompletionCallback cb, ProgressCallback progress_cb={})
const char * tr(const char *msgid)
std::string trs(const char *msgid)
std::string load_license_text()
std::string format_elapsed(std::chrono::steady_clock::duration elapsed)
std::string read_text_file(const std::string &path)
void set_button_with_svg_icon(Gtk::Button &button, const std::string &svg_path, const Glib::ustring &text)
std::string resource(const std::string &rel)
std::vector< std::string > logs
std::vector< std::string > exported_images
std::vector< std::string > recovered_files
std::vector< ScanResult > results
std::vector< std::string > logs