Skip to content
API Documentation
BastionGuard™
LiveScanDialog.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#include "LiveScanDialog.hpp"
22#include "../Resource.hpp"
23#include <fstream>
24#include <glibmm/i18n.h>
25#include <glibmm/miscutils.h>
26
27// Sceglie il CSS in base al tema salvato in ~/.config/BastionGuard/theme.conf.
28// Stessa logica di StyleProvider::load_style(), così il popup di
29// scansione USB segue il tema (dark/light) impostato dall'utente.
30static std::string usb_live_resolve_theme_css() {
31 const std::string conf =
32 Glib::get_home_dir() + "/.config/BastionGuard/theme.conf";
33 std::ifstream tf(conf);
34 std::string theme;
35 if (tf >> theme && theme == "dark")
36 return "BastionGuard-dark.css";
37 return "BastionGuard.css";
38}
39
40LiveScanDialog::LiveScanDialog(Gtk::Window* parent, const std::string& path)
41 : root_(Gtk::Orientation::VERTICAL, 16),
42 alive_(BastionGuard::make_alive_flag())
43{
44 set_title("");
45 set_default_size(620, 420);
46 set_resizable(false);
47 set_modal(true);
48 if (parent) set_transient_for(*parent);
49
50 add_css_class("app-dialog");
51 add_css_class("main-window");
52 add_css_class("app-window");
53
54 try {
55 auto css = Gtk::CssProvider::create();
56 css->load_from_path(resource(usb_live_resolve_theme_css()));
57 Gtk::StyleContext::add_provider_for_display(
58 Gdk::Display::get_default(), css,
59 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
60 );
61 } catch (...) {}
62
63 auto *header = Gtk::make_managed<Gtk::HeaderBar>();
64 header->set_show_title_buttons(false);
65 header->set_decoration_layout("");
66 header->add_css_class("custom-headerbar");
67
68 auto *close_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
69 close_box->set_margin_end(6);
70
71 try {
72 auto px = Gdk::Pixbuf::create_from_file(resource("icon-close.png"), 28, 28, true);
73 auto btn = Gtk::make_managed<Gtk::Button>();
74 btn->set_child(*Gtk::make_managed<Gtk::Image>(px));
75 btn->add_css_class("header-button");
76 btn->signal_clicked().connect([this]() { hide(); });
77 close_box->append(*btn);
78 } catch (...) {}
79
80 header->pack_end(*close_box);
81
82 auto *header_wrap = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
83 header_wrap->append(*header);
84 set_titlebar(*header_wrap);
85
86 root_.set_margin(20);
87
88 auto title = Gtk::make_managed<Gtk::Label>(
89 _("Scansione della periferica USB in corso")
90 );
91 title->add_css_class("title");
92 title->set_halign(Gtk::Align::CENTER);
93 root_.append(*title);
94
95 auto *icon = Gtk::make_managed<Gtk::Picture>();
96 icon->set_halign(Gtk::Align::CENTER);
97 icon->set_margin_bottom(10);
98 try {
99 auto pb = Gdk::Pixbuf::create_from_file(resource("icons/scan.png"));
100 auto scaled = pb->scale_simple(64, 64, Gdk::InterpType::BILINEAR);
101 icon->set_paintable(Gdk::Texture::create_for_pixbuf(scaled));
102 } catch (...) {}
103 root_.append(*icon);
104
105 auto device_label = Gtk::make_managed<Gtk::Label>(
106 _("Percorso analizzato:\n") + path
107 );
108 device_label->set_wrap(true);
109 device_label->set_halign(Gtk::Align::CENTER);
110 root_.append(*device_label);
111
112 progress_.set_pulse_step(0.1);
113 progress_.set_show_text(true);
114 progress_.set_text(_("Analisi in corso..."));
115 root_.append(progress_);
116
117 text_view_.set_editable(false);
118 scroller_.set_child(text_view_);
119 scroller_.set_vexpand(true);
120 scroller_.add_css_class("scroller");
121 root_.append(scroller_);
122 btn_cancel_.set_label(_("Annulla"));
123 btn_cancel_.add_css_class("btn-danger");
124 btn_cancel_.set_halign(Gtk::Align::CENTER);
125 btn_cancel_.signal_clicked().connect([this]() {
126 cancelled_.store(true);
127 btn_cancel_.set_sensitive(false);
128 progress_.set_text(_("Interruzione in corso..."));
129 });
130 root_.append(btn_cancel_);
131
132 set_child(root_);
133
134 {
135 auto alive = alive_;
136 Glib::signal_timeout().connect(
137 [this, alive]() -> bool {
138 if (!alive || !alive->load(std::memory_order_acquire)) {
139 return false; // oggetto morto: disconnette il timer
140 }
141 if (finished_) return false;
142 progress_.pulse();
143 return true;
144 },
145 120
146 );
147 }
148
149 present();
150}
151
152
153
155{
156 // Segnala ai lambda in volo (signal_idle non ancora eseguito,
157 // timeout pulse pending) che l'oggetto sta per sparire.
158 if (alive_) alive_->store(false, std::memory_order_release);
159}
160
161
162void LiveScanDialog::append_output(const std::string& line)
163{
164 {
165 std::lock_guard<std::mutex> lock(mtx_);
166 pending_lines_.push_back(line + "\n");
167 }
168
169 if (!flush_pending_.exchange(true)) {
170 auto alive = alive_;
171 Glib::signal_idle().connect_once([this, alive]() {
172 BastionGuard::if_alive(alive, [this]() {
174 });
175 });
176 }
177}
178
179
181{
182 flush_pending_.store(false);
183
184 std::vector<std::string> lines;
185 {
186 std::lock_guard<std::mutex> lock(mtx_);
187 lines.swap(pending_lines_);
188 }
189
190 if (lines.empty())
191 return;
192
193 auto buf = text_view_.get_buffer();
194 Gtk::TextIter it = buf->end();
195
196 for (auto& l : lines) {
197 buf->insert(it, l);
198 it = buf->end();
199 full_output_ += l;
200 }
201
202 if (auto adj = scroller_.get_vadjustment())
203 adj->set_value(adj->get_upper());
204}
205
206
208{
209 finished_.store(true);
210
211 // Forza ultimo flush
212 flush_pending_.store(true);
213
214 auto alive = alive_;
215 Glib::signal_idle().connect_once([this, alive]() {
216 BastionGuard::if_alive(alive, [this]() {
218 progress_.set_fraction(1.0);
219 progress_.set_text(_("Completato"));
220 });
221 });
222}
223
224
226{
227 std::lock_guard<std::mutex> lock(mtx_);
228 return full_output_;
229}
#define _(s)
static std::string usb_live_resolve_theme_css()
std::vector< std::string > pending_lines_
std::string full_output_
LiveScanDialog(Gtk::Window *parent, const std::string &path)
Gtk::Button btn_cancel_
std::atomic< bool > flush_pending_
Gtk::TextView text_view_
Gtk::ProgressBar progress_
BastionGuard::AliveFlag alive_
void append_output(const std::string &line)
std::atomic< bool > finished_
std::string get_full_output() const
std::atomic< bool > cancelled_
Gtk::ScrolledWindow scroller_
~LiveScanDialog() override
void if_alive(const AliveFlag &alive, Fn &&fn)
std::string resource(const std::string &rel)
Definition Resource.hpp:31