Skip to content
API Documentation
BastionGuard™
MainWindow.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
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.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
21
22#include "MainWindow.hpp"
23#include "ClamdConfig.hpp"
24#include "Resource.hpp"
25#include "SettingsWindow.hpp"
26#include "DistroUtils.hpp"
27#include <gdkmm/pixbuf.h>
28#include <glibmm/fileutils.h>
29#include <glib/gi18n.h>
30#include <filesystem>
31#include <fstream>
32#include <iostream>
33#include <algorithm>
34#include <regex>
35#include <vector>
36
37
38
40 set_title("");
41 set_resizable(true);
42 set_default_size(1000, 680);
43 set_size_request(800, 560);
44
45 std::vector<std::string> conf_paths = {
46 "/etc/clamd/clamd.conf",
47 "/etc/clamav/clamd.conf",
48 "/etc/clamd.d/clamd.conf",
49 "/etc/clamd.conf"
50 };
51
52 bool loaded = false;
53 for (const auto& path : conf_paths) {
54 if (clamdConfig_.load(path)) {
55 std::cout << _("✔ Caricato config da: ") << path << std::endl;
56 loaded = true;
57 break;
58 }
59 }
60
61 if (!loaded) {
62 std::cerr << _("❌ Errore: impossibile leggere clamd.conf in nessuno dei percorsi noti") << "\n";
63 }
64
65
66
68
69
70 stack_.get_style_context()->add_class("content");
71 stack_.set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
72 stack_.set_transition_duration(220);
73 stack_.add(dashboard_page_, "dashboard");
74 stack_.add(scan_page_, "scan");
75 samba_page_ = Gtk::make_managed<SambaPage>();
76 stack_.add(*samba_page_, "samba");
77 anti_ransomware_page_ = Gtk::make_managed<AntiRansomwarePage>(*this);
78 stack_.add(*anti_ransomware_page_, "antiransomware");
79 backup_page_ = Gtk::make_managed<BackupPage>();
80 stack_.add(*backup_page_, "backup");
81 stack_.add(log_page_, "log");
82#if BASTIONGUARD_HAS_CEF
84 bank_page_ = Gtk::make_managed<BankPage>();
85 stack_.add(*bank_page_, "bank");
86 }
87#endif
88 privacy_page_ = Gtk::make_managed<PrivacyPage>();
89 stack_.add(*privacy_page_, "privacy");
90 rootguard_page_ = Gtk::make_managed<RootGuardPage>();
91 stack_.add(*rootguard_page_, "rootguard");
92 stack_.add(quarantine_page_, "quarantine");
93 usb_page_ = Gtk::make_managed<USBScanPage>();
94 stack_.add(*usb_page_, "usbscan");
95 stack_.add(update_page_, "update");
96 stack_.add(about_page_, "about");
97 stack_.add(donate_page_, "donate");
98 password_manager_page_ = Gtk::make_managed<PasswordManagerPage>();
99 stack_.add(*password_manager_page_, "passwordmanager");
100 identity_leak_page_ = Gtk::make_managed<IdentityLeakPage>(*this);
101 stack_.add(*identity_leak_page_, "identityleak");
102
103 phishing_page_ = Gtk::make_managed<PhishingPage>();
104 stack_.add(*phishing_page_, "phishing");
105 vpn_page_ = Gtk::make_managed<VpnPage>();
106 stack_.add(*vpn_page_, "vpn");
107
109 mainBox.append(stack_);
110 set_child(mainBox);
111
112
113 auto get_cvd_version = [](const std::string& path) -> std::string {
114 std::ifstream file(path, std::ios::binary);
115 if (!file.is_open()) return "";
116
117 std::string header;
118 header.resize(512);
119 file.read(&header[0], 512);
120
121 std::regex re("ClamAV-VDB:.*:([0-9]+):");
122 std::smatch match;
123 if (std::regex_search(header, match, re)) {
124 return match[1];
125 }
126 return "";
127 };
128
129 std::string db_version;
130 std::string daily = get_cvd_version("/var/lib/clamav/daily.cvd");
131 std::string main = get_cvd_version("/var/lib/clamav/main.cvd");
132 std::string bytecode = get_cvd_version("/var/lib/clamav/bytecode.cvd");
133
134 if (!daily.empty()) db_version += "daily: " + daily + " ";
135 if (!main.empty()) db_version += "main: " + main + " ";
136 if (!bytecode.empty()) db_version += "bytecode: " + bytecode + " ";
137 if (db_version.empty()) db_version = _("sconosciuta");
138
139 bool on_access_enabled = true;
140 dashboard_page_.update_from_logs("", db_version, on_access_enabled);
142}
143
144
146 auto header = Gtk::make_managed<Gtk::HeaderBar>();
147 header->set_show_title_buttons(false);
148 header->set_decoration_layout("");
149 header->get_style_context()->add_class("custom-headerbar");
150
151
152 auto header_left = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
153 // La larghezza è gestita dal CSS con min-width: 240px sul .header-left
154 header_left->set_hexpand(false);
155 header_left->get_style_context()->add_class("header-left");
156 header->pack_start(*header_left);
157
158
159 auto header_spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
160 header_spacer->set_hexpand(true);
161 header->pack_start(*header_spacer);
162
163
164 auto header_right = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
165 header_right->get_style_context()->add_class("header-right");
166
167 try {
168 auto pix_min = Gdk::Pixbuf::create_from_file(resource("icon-minimize.png"), 32, 32, true);
169 auto btn_min = Gtk::make_managed<Gtk::Button>();
170 btn_min->set_child(*Gtk::make_managed<Gtk::Image>(pix_min));
171 btn_min->get_style_context()->add_class("header-button");
172 header_right->append(*btn_min);
173 btn_min->signal_clicked().connect([this]() { minimize(); });
174 } catch (...) {
175 std::cerr << _("⚠ Errore caricamento icona minimize") << "\n";
176 }
177
178 try {
179 auto pix_close = Gdk::Pixbuf::create_from_file(resource("icon-close.png"), 32, 32, true);
180 auto btn_close = Gtk::make_managed<Gtk::Button>();
181 btn_close->set_child(*Gtk::make_managed<Gtk::Image>(pix_close));
182 btn_close->get_style_context()->add_class("header-button");
183 header_right->append(*btn_close);
184
185 btn_close->signal_clicked().connect([this]() {
186 std::cout << _("[MainWindow] Pulsante close → nascondo nella tray") << "\n";
187 this->hide();
188 });
189 } catch (...) {
190 std::cerr << _("⚠ Errore caricamento icona close") << "\n";
191 }
192
193 header->pack_end(*header_right);
194 set_titlebar(*header);
195}
196
197
199
200 // Ordine pagine per direzione animazione
201 static const std::vector<std::string> kPageOrder = {
202 "dashboard",
203 "scan","antiransomware",
204#if BASTIONGUARD_HAS_CEF
205 "bank",
206#endif
207 "privacy","rootguard","usbscan","samba",
208 "log","quarantine","identityleak","passwordmanager","backup","update",
209 "phishing",
210 "about","donate"
211 };
212
213 // Tracciamento revealer aperti — uno solo alla volta
214 // Usiamo un vettore di puntatori ai revealer attivi
215 auto open_revealers = std::make_shared<std::vector<Gtk::Revealer*>>();
216
217 auto sidebar_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
218 sidebar_box->set_size_request(240, -1);
219 sidebar_box->get_style_context()->add_class("sidebar");
220
221 // ── Header: logo + titolo ───────────────────────────────────────────
222 {
223 auto hdr = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
224 hdr->get_style_context()->add_class("sidebar-header");
225 try {
226 auto pb = Gdk::Pixbuf::create_from_file(resource("logo-sidebar.png"), 128, 128, true);
227 auto logo = Gtk::make_managed<Gtk::Image>(pb);
228 hdr->append(*logo);
229 } catch (...) {}
230 auto lbl = Gtk::make_managed<Gtk::Label>("BastionGuard™ Endpoint");
231 lbl->set_halign(Gtk::Align::START);
232 lbl->get_style_context()->add_class("sidebar-title");
233 hdr->append(*lbl);
234 sidebar_box->append(*hdr);
235 }
236
237 // ── navigate_to: animazione direzionale ────────────────────────────
238 auto navigate_to = [this, &kPageOrder](const std::string& page) {
239 const std::string cur = stack_.get_visible_child_name();
240 auto itC = std::find(kPageOrder.begin(), kPageOrder.end(), cur);
241 auto itD = std::find(kPageOrder.begin(), kPageOrder.end(), page);
242 if (itC != kPageOrder.end() && itD != kPageOrder.end()) {
243 if (itD > itC)
244 stack_.set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT);
245 else if (itD < itC)
246 stack_.set_transition_type(Gtk::StackTransitionType::SLIDE_RIGHT);
247 }
248 stack_.set_visible_child(page);
249 };
250
251 // ── make_leaf: voce singola con icona ──────────────────────────────
252 auto make_leaf = [&](const std::string& icon_path,
253 const std::string& label_text,
254 const std::string& page_name,
255 bool indent = false) -> Gtk::Button*
256 {
257 auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
258 if (indent) box->set_margin_start(14);
259
260 auto ib = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
261 ib->set_size_request(16, 16);
262 ib->set_valign(Gtk::Align::CENTER);
263 auto pic = Gtk::make_managed<Gtk::Picture>();
264 try {
265 auto pb = Gdk::Pixbuf::create_from_file(icon_path);
266 auto sc = pb->scale_simple(16, 16, Gdk::InterpType::BILINEAR);
267 pic->set_paintable(Gdk::Texture::create_for_pixbuf(sc));
268 pic->set_content_fit(Gtk::ContentFit::FILL);
269 } catch (...) {}
270 ib->append(*pic);
271 box->append(*ib);
272
273 auto lbl = Gtk::make_managed<Gtk::Label>(_(label_text.c_str()));
274 lbl->set_halign(Gtk::Align::START);
275 lbl->get_style_context()->add_class("sidebar-group-label");
276 box->append(*lbl);
277
278 auto btn = Gtk::make_managed<Gtk::Button>();
279 btn->set_child(*box);
280 btn->get_style_context()->add_class("sidebar-button");
281 sidebar_buttons_.push_back(btn);
282
283 btn->signal_clicked().connect([this, btn, page_name, navigate_to]() {
284 for (auto b : sidebar_buttons_)
285 b->get_style_context()->remove_class("active-sidebar-button");
286 btn->get_style_context()->add_class("active-sidebar-button");
287 navigate_to(page_name);
288 });
289 return btn;
290 };
291
292 // ── make_group: gruppo espandibile, uno alla volta ─────────────────
293 auto make_group = [&](const std::string& icon_path,
294 const std::string& label_text) -> Gtk::Box*
295 {
296 auto arrow = Gtk::make_managed<Gtk::Label>("▸");
297 arrow->set_halign(Gtk::Align::END);
298 arrow->set_hexpand(true);
299 arrow->get_style_context()->add_class("sidebar-arrow");
300
301 auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
302 auto ib = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
303 ib->set_size_request(16, 16);
304 ib->set_valign(Gtk::Align::CENTER);
305 auto pic = Gtk::make_managed<Gtk::Picture>();
306 try {
307 auto pb = Gdk::Pixbuf::create_from_file(icon_path);
308 auto sc = pb->scale_simple(16, 16, Gdk::InterpType::BILINEAR);
309 pic->set_paintable(Gdk::Texture::create_for_pixbuf(sc));
310 pic->set_content_fit(Gtk::ContentFit::FILL);
311 } catch (...) {}
312 ib->append(*pic);
313 box->append(*ib);
314
315 auto lbl = Gtk::make_managed<Gtk::Label>(_(label_text.c_str()));
316 lbl->set_halign(Gtk::Align::START);
317 box->append(*lbl);
318 box->append(*arrow);
319
320 auto hbtn = Gtk::make_managed<Gtk::Button>();
321 hbtn->set_child(*box);
322 hbtn->get_style_context()->add_class("sidebar-group-button");
323
324 auto revealer = Gtk::make_managed<Gtk::Revealer>();
325 revealer->set_transition_type(Gtk::RevealerTransitionType::SLIDE_DOWN);
326 revealer->set_transition_duration(200);
327 revealer->set_reveal_child(false); // chiuso all'avvio
328
329 auto children = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
330 revealer->set_child(*children);
331
332 // Registra questo revealer nella lista
333 open_revealers->push_back(revealer);
334
335 hbtn->signal_clicked().connect([revealer, arrow, open_revealers]() {
336 bool opening = !revealer->get_reveal_child();
337 // Chiudi tutti gli altri
338 for (auto* r : *open_revealers) {
339 if (r != revealer && r->get_reveal_child()) {
340 r->set_reveal_child(false);
341 // Aggiorna la freccia del revealer che stiamo chiudendo
342 // (non abbiamo accesso diretto alla freccia degli altri,
343 // ma il CSS transition lo gestisce visivamente)
344 }
345 }
346 revealer->set_reveal_child(opening);
347 arrow->set_text(opening ? "▾" : "▸");
348 });
349
350 sidebar_box->append(*hbtn);
351 sidebar_box->append(*revealer);
352 return children;
353 };
354
355 // ═══════════════════════════════════════════════════════════════════
356 // Dashboard
357 // ═══════════════════════════════════════════════════════════════════
358 auto* db_btn = make_leaf(resource("icons/dashboard.svg"), _("Dashboard"), "dashboard");
359 dashboard_button_ = db_btn;
360 sidebar_box->append(*db_btn);
361
362 // ═══════════════════════════════════════════════════════════════════
363 // Gruppo: Protezione
364 // ═══════════════════════════════════════════════════════════════════
365 auto* grp_prot = make_group(resource("icons/scan.svg"), _("Protezione"));
366 grp_prot->append(*make_leaf(resource("icons/scan.svg"), _("Scansione in tempo reale"), "scan", true));
367 grp_prot->append(*make_leaf(resource("icons/ransomware.svg"), _("Protezione Ransomware"), "antiransomware", true));
368#if BASTIONGUARD_HAS_CEF
370 grp_prot->append(*make_leaf(resource("icons/bank.svg"), _("Banche"), "bank", true));
371 }
372#endif
373 grp_prot->append(*make_leaf(resource("icons/privacy.svg"), _("Privacy Webcam"), "privacy", true));
374 grp_prot->append(*make_leaf(resource("icons/rootguard.svg"), _("RootGuard"), "rootguard", true));
375 grp_prot->append(*make_leaf(resource("icons/usb.svg"), _("Periferiche USB"), "usbscan", true));
376 grp_prot->append(*make_leaf(resource("icons/samba.svg"), _("Samba"), "samba", true));
377
378 // ═══════════════════════════════════════════════════════════════════
379 // Gruppo: Sicurezza
380 // ═══════════════════════════════════════════════════════════════════
381 auto* grp_sec = make_group(resource("icons/log.svg"), _("Sicurezza"));
382 grp_sec->append(*make_leaf(resource("icons/log.svg"), _("Log"), "log", true));
383 grp_sec->append(*make_leaf(resource("icons/quarantine.svg"), _("Quarantena"), "quarantine", true));
384 grp_sec->append(*make_leaf(resource("icons/identity.svg"), _("Violazioni Identità"), "identityleak", true));
385 grp_sec->append(*make_leaf(resource("icons/password.svg"), _("Password Manager"), "passwordmanager", true));
386 grp_sec->append(*make_leaf(resource("icons/backup.svg"), _("Backup"), "backup", true));
387 grp_sec->append(*make_leaf(resource("icons/update.svg"), _("Aggiornamenti"), "update", true));
388
389 // ═══════════════════════════════════════════════════════════════════
390 // Phishing (voce singola)
391 // ═══════════════════════════════════════════════════════════════════
392 sidebar_box->append(*make_leaf(resource("icons/phishing.svg"), _("Phishing Search"), "phishing"));
393
394 // ═══════════════════════════════════════════════════════════════════
395 // VPN (voce singola)
396 // ═══════════════════════════════════════════════════════════════════
397 sidebar_box->append(*make_leaf(resource("icons/vpn.svg"), _("VPN WireGuard"), "vpn"));
398
399 // ═══════════════════════════════════════════════════════════════════
400 // Impostazioni (apre finestra separata)
401 // ═══════════════════════════════════════════════════════════════════
402 {
403 auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
404 auto ib = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
405 ib->set_size_request(16, 16);
406 ib->set_valign(Gtk::Align::CENTER);
407 auto pic = Gtk::make_managed<Gtk::Picture>();
408 try {
409 auto pb = Gdk::Pixbuf::create_from_file(resource("icons/settings.svg"));
410 auto sc = pb->scale_simple(16, 16, Gdk::InterpType::BILINEAR);
411 pic->set_paintable(Gdk::Texture::create_for_pixbuf(sc));
412 } catch (...) {}
413 ib->append(*pic);
414 box->append(*ib);
415 auto lbl = Gtk::make_managed<Gtk::Label>(_("Impostazioni"));
416 lbl->set_halign(Gtk::Align::START);
417 box->append(*lbl);
418 auto btn = Gtk::make_managed<Gtk::Button>();
419 btn->set_child(*box);
420 btn->get_style_context()->add_class("sidebar-button");
421 btn->signal_clicked().connect([this]() { open_settings_window(); });
422 sidebar_box->append(*btn);
423 }
424
425 // Spacer + separatore
426 auto spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
427 spacer->set_vexpand(true);
428 sidebar_box->append(*spacer);
429
430 auto sep = Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL);
431 sep->get_style_context()->add_class("sidebar-separator");
432 sidebar_box->append(*sep);
433
434 sidebar_box->append(*make_leaf(resource("icons/about.svg"), _("Informazioni"), "about"));
435 sidebar_box->append(*make_leaf(resource("icons/donate.svg"), _("Donazioni"), "donate"));
436
437 mainBox.append(*sidebar_box);
438}
439
441{
442 for (auto b : sidebar_buttons_)
443 b->get_style_context()->remove_class("active-sidebar-button");
444
445 if (btn)
446 btn->get_style_context()->add_class("active-sidebar-button");
447}
448
449
450
452 #if GTKMM_CHECK_VERSION(4,10,0)
453 this->hide();
454 #endif
455}
456
457
459 std::cout << _("[MainWindow] Nascondo finestra nella tray invece di chiudere") << "\n";
460 this->hide();
461 return true;
462}
463
464
466 try {
467 stack_.set_visible_child("dashboard");
469
470 // grab_focus su un widget non ancora realized/mapped emette
471 // un g_critical fatale (con G_DEBUG=fatal-criticals). Posticipalo
472 // a quando la finestra è effettivamente mostrata.
473 if (dashboard_page_.get_realized() && dashboard_page_.get_mapped()) {
474 dashboard_page_.grab_focus();
475 } else {
476 Glib::signal_idle().connect_once([this]() {
477 try {
478 if (dashboard_page_.get_realized() && dashboard_page_.get_mapped()) {
479 dashboard_page_.grab_focus();
480 }
481 } catch (...) {}
482 });
483 }
484
485 std::cout << _("[MainWindow] Ritorno alla Dashboard") << std::endl;
486 } catch (const std::exception& e) {
487 std::cerr << _("⚠ Errore durante il ritorno alla Dashboard: ") << e.what() << std::endl;
488 } catch (...) {
489 std::cerr << _("⚠ Errore sconosciuto durante il ritorno alla Dashboard") << std::endl;
490 }
491}
492
494{
495 if (settings_window_) {
496 settings_window_->present();
497 return;
498 }
499
501 settings_window_->set_transient_for(*this);
502 settings_window_->set_modal(true);
503
504 settings_window_->signal_close_request().connect(
505 [this]() -> bool {
506 settings_window_->hide();
507 return true;
508 },
509 false
510 );
511
512 settings_window_->present();
513}
#define _(s)
int main(int argc, char *argv[])
bool load(const std::string &path)
void update_from_logs(const std::string &log_path, const Glib::ustring &db_version, bool on_access_enabled)
PasswordManagerPage * password_manager_page_
BackupPage * backup_page_
USBScanPage * usb_page_
AboutPage about_page_
DonatePage donate_page_
Gtk::Button * dashboard_button_
DashboardPage dashboard_page_
PhishingPage * phishing_page_
bool on_close_request() override
void minimize()
LogPage log_page_
BankPage * bank_page_
IdentityLeakPage * identity_leak_page_
Gtk::Box mainBox
ClamdConfig clamdConfig_
SettingsWindow * settings_window_
PrivacyPage * privacy_page_
std::vector< Gtk::Button * > sidebar_buttons_
UpdatePage update_page_
void build_headerbar()
QuarantinePage quarantine_page_
void open_settings_window()
void showDashboard()
void activate_sidebar_button(Gtk::Button *btn)
AntiRansomwarePage * anti_ransomware_page_
void build_sidebar()
RootGuardPage * rootguard_page_
Gtk::Stack stack_
void build_sidebar(ClamdConfig &clamdConfig)
VpnPage * vpn_page_
ScanPage scan_page_
SambaPage * samba_page_
SettingsWindow(ClamdConfig &config)
std::string resource(const std::string &rel)
Definition Resource.hpp:31