181 : Gtk::Box(Gtk::Orientation::VERTICAL, 12)
187 auto title_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
188 title_box->add_css_class(
"secure-headerbar");
189 title_box->set_margin_bottom(8);
191 lbl_title_.set_text(
_(
"🔒 Privacy Webcam / Microfono"));
195 lbl_title_.add_css_class(
"secure-header-title");
204 std::string config_path = Glib::get_home_dir() +
"/.config/BastionGuard/privacy.conf";
205 if (Glib::file_test(config_path, Glib::FileTest::EXISTS)) {
206 auto keyfile = Glib::KeyFile::create();
207 keyfile->load_from_file(config_path);
208 bool auto_refresh_enabled =
false;
209 if (keyfile->has_key(
"privacy",
"auto_refresh"))
210 auto_refresh_enabled = keyfile->get_boolean(
"privacy",
"auto_refresh");
214 std::cerr <<
_(
"[Privacy] Errore lettura auto_refresh") << std::endl;
222 auto lbl_switch = Gtk::make_managed<Gtk::Label>(
_(
"Abilita/Disabilita Webcam e Microfono"));
223 lbl_switch->set_halign(Gtk::Align::CENTER);
227 auto control_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
228 control_box->set_halign(Gtk::Align::CENTER);
237 append(*control_box);
246 write_log(
_(
"Richiesto sblocco manuale dei dispositivi"));
268 Glib::signal_idle().connect_once([
this, alive]() {
271 std::string base_dir = Glib::get_home_dir() +
"/.local/share/BastionGuard/logs";
272 auto t = std::time(
nullptr);
274 localtime_r(&t, &tm);
276 std::strftime(datebuf,
sizeof(datebuf),
"%Y-%m-%d", &tm);
277 std::string log_path = base_dir +
"/privacyd_" + std::string(datebuf) +
".log";
279 if (fs::exists(log_path)) {
280 std::ifstream log_file(log_path);
281 std::stringstream buffer;
282 buffer << log_file.rdbuf();
286 lbl_status_.set_text(
_(
"📝 Nessun log disponibile per oggi."));
289 lbl_status_.set_text(
_(
"⚠️ Errore durante il caricamento del log."));
302 Glib::signal_idle().connect_once([
this, alive_ctor]() {
303 if (!alive_ctor || !alive_ctor->load(std::memory_order_acquire))
return;
304 lbl_status_.set_text(
_(
"⏳ Inizializzazione del servizio privacy..."));
305 write_log(
_(
"=== Avvio BastionGuard PrivacyPage ==="));
307 std::thread([
this, alive]() {
308 if (!alive || !alive->load(std::memory_order_acquire))
return;
312 Glib::signal_idle().connect_once([
this, alive]() {
315 lbl_status_.set_text(
_(
"🕵️ Monitoraggio privacy attivo"));
357 std::vector<std::string> result;
358 const std::vector<std::string> prefixes = {
"/dev/video",
"/dev/snd/pcm"};
364 fs::directory_iterator proc_it(
"/proc", ec);
370 for (; proc_it != fs::directory_iterator(); proc_it.increment(ec)) {
377 const auto& pid_entry = *proc_it;
379 std::error_code ec_dir;
380 if (!pid_entry.is_directory(ec_dir) || ec_dir)
continue;
382 const std::string pid_str = pid_entry.path().filename().string();
383 if (!std::all_of(pid_str.begin(), pid_str.end(), ::isdigit))
continue;
385 const std::string fd_path =
"/proc/" + pid_str +
"/fd";
386 std::error_code ec_exists;
387 if (!fs::exists(fd_path, ec_exists) || ec_exists)
continue;
390 std::string comm, exe, cmdline;
391 bool proc_info_loaded =
false;
395 std::error_code ec_fd;
396 fs::directory_iterator fd_it(
398 fs::directory_options::skip_permission_denied,
402 for (; fd_it != fs::directory_iterator(); fd_it.increment(ec_fd)) {
405 const auto& fd_entry = *fd_it;
407 std::error_code ec_link;
408 auto link_target = fs::read_symlink(fd_entry.path(), ec_link);
409 if (ec_link)
continue;
410 std::string target = link_target.string();
412 bool matches =
false;
413 for (
const auto& prefix : prefixes) {
414 if (target.rfind(prefix, 0) == 0) {
419 if (!matches)
continue;
421 if (!proc_info_loaded) {
423 if (comm.empty()) comm =
"(sconosciuto)";
425 cmdline =
read_cmdline(
"/proc/" + pid_str +
"/cmdline");
426 proc_info_loaded =
true;
435 result.push_back(comm +
" (PID=" + pid_str +
")");
440 std::sort(result.begin(), result.end());
441 result.erase(std::unique(result.begin(), result.end()), result.end());
512 win->set_default_size(480, 230);
513 win->set_resizable(
false);
514 win->set_modal(
false);
515 win->add_css_class(
"BastionGuard-popup");
516 win->add_css_class(
"BastionGuard-dialog");
519 if (
auto* root =
dynamic_cast<Gtk::Window*
>(get_root()))
520 win->set_transient_for(*root);
523 auto *header = Gtk::make_managed<Gtk::HeaderBar>();
524 header->set_show_title_buttons(
false);
525 header->set_decoration_layout(
"");
526 header->add_css_class(
"custom-headerbar");
529 auto *left = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
530 left->set_size_request(240, -1);
532 auto *title_lbl = Gtk::make_managed<Gtk::Label>(
_(
"⚠️ Accesso dispositivo rilevato"));
533 title_lbl->add_css_class(
"BastionGuard-title");
534 title_lbl->set_halign(Gtk::Align::START);
535 left->append(*title_lbl);
537 header->pack_start(*left);
540 auto *center = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
541 center->set_hexpand(
true);
542 header->pack_start(*center);
545 auto *right = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
548 auto pmin = Gdk::Pixbuf::create_from_file(
resource(
"icon-minimize.png"), 28, 28,
true);
549 auto *btn_min = Gtk::make_managed<Gtk::Button>();
550 btn_min->set_child(*Gtk::make_managed<Gtk::Image>(pmin));
551 btn_min->add_css_class(
"header-button");
552 btn_min->signal_clicked().connect([win]() { win->minimize(); });
553 right->append(*btn_min);
557 auto pclose = Gdk::Pixbuf::create_from_file(
resource(
"icon-close.png"), 28, 28,
true);
558 auto *btn_close = Gtk::make_managed<Gtk::Button>();
559 btn_close->set_child(*Gtk::make_managed<Gtk::Image>(pclose));
560 btn_close->add_css_class(
"header-button");
561 btn_close->signal_clicked().connect([
this, win]() {
565 Glib::signal_idle().connect_once([
this, alive]() {
569 right->append(*btn_close);
572 header->pack_end(*right);
574 win->set_titlebar(*header);
577 auto *vbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
578 vbox->set_margin(20);
579 win->set_child(*vbox);
581 auto *msg = Gtk::make_managed<Gtk::Label>(
582 Glib::ustring::compose(
583 _(
"<b>%1</b> sta tentando di accedere a webcam o microfono."),
587 msg->set_use_markup(
true);
589 msg->set_halign(Gtk::Align::CENTER);
593 auto *lbl_pending = Gtk::make_managed<Gtk::Label>(
594 Glib::ustring::compose(
_(
"🕒 %1 altri processi in attesa..."), pending));
595 lbl_pending->set_halign(Gtk::Align::CENTER);
596 vbox->append(*lbl_pending);
600 auto *btn_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 12);
601 btn_box->set_halign(Gtk::Align::CENTER);
603 auto *btn_allow = Gtk::make_managed<Gtk::Button>(
_(
"✔ Consenti"));
604 auto *btn_deny = Gtk::make_managed<Gtk::Button>(
_(
"✖ Nega"));
606 btn_allow->add_css_class(
"btn-primary");
607 btn_deny->add_css_class(
"btn-danger");
609 btn_box->append(*btn_allow);
610 btn_box->append(*btn_deny);
611 vbox->append(*btn_box);
613 btn_allow->signal_clicked().connect([
this, proc, win]() {
614 write_log(Glib::ustring::compose(
_(
"Accesso CONSENTITO a %1"), proc));
620 Glib::signal_idle().connect_once([
this, alive]() {
626 btn_deny->signal_clicked().connect([
this, proc, win]() {
629 _(
"Accesso NEGATO a %1 — blocco webcam/microfono"), proc));
639 std::thread([
this, alive]() {
642 Glib::signal_idle().connect_once([
this, alive, ok]() {
645 write_log(
_(
"❌ ERRORE: impossibile applicare il blocco tramite helper!"));
646 lbl_status_.set_text(
_(
"❌ Errore: impossibile bloccare webcam/microfono"));
648 write_log(
_(
"🔒 Webcam/microfono BLOCCATI tramite helper (popup)"));
649 lbl_status_.set_text(
_(
"🔒 Webcam e microfono bloccati"));
662 Glib::signal_idle().connect_once([
this, alive2]() {
686 auto keyfile = Glib::KeyFile::create();
687 std::string config_dir = Glib::get_home_dir() +
"/.config/BastionGuard";
688 std::string config_path = config_dir +
"/privacy.conf";
690 if (!fs::exists(config_dir))
691 fs::create_directories(config_dir);
693 if (Glib::file_test(config_path, Glib::FileTest::EXISTS))
694 keyfile->load_from_file(config_path);
696 keyfile->set_boolean(
"privacy",
"auto_refresh", enabled);
697 keyfile->save_to_file(config_path);
698 write_log(enabled ?
_(
"Auto aggiornamento attivato (salvato in config)")
699 :
_(
"Auto aggiornamento disattivato (salvato in config)"));
700 }
catch (
const Glib::Error& e) {
701 std::cerr <<
_(
"[Privacy] Errore salvataggio auto_refresh: ") << e.what() << std::endl;
706 lbl_status_.set_text(
_(
"⏳ Auto aggiornamento attivo ogni 5 minuti."));
716 lbl_status_.set_text(
_(
"🛑 Auto aggiornamento disattivato."));
725 std::thread([
this, alive]() {
726 if (!alive || !alive->load(std::memory_order_acquire))
return;
730 std::ostringstream oss;
731 if (processes.empty()) {
732 oss <<
_(
"Nessun processo sta utilizzando webcam o microfono.") <<
"\n\n";
734 oss <<
_(
"📋 Processi attivi:\n\n");
735 for (
const auto &p : processes)
736 oss <<
" • " << p <<
"\n";
741 oss <<
"────────────────────────────────────────────\n";
742 oss <<
_(
"📜 Log giornaliero:\n\n");
745 std::string base_dir = Glib::get_home_dir() +
"/.local/share/BastionGuard/logs";
746 auto t = std::time(
nullptr);
748 localtime_r(&t, &tm);
750 std::strftime(datebuf,
sizeof(datebuf),
"%Y-%m-%d", &tm);
751 std::string log_path = base_dir +
"/privacyd_" + std::string(datebuf) +
".log";
753 if (fs::exists(log_path)) {
754 std::ifstream log_file(log_path);
755 if (log_file.is_open()) {
756 std::stringstream buffer;
757 buffer << log_file.rdbuf();
760 oss <<
_(
"⚠️ Impossibile leggere il log giornaliero.") <<
"\n";
763 oss <<
_(
"📝 Nessun log disponibile per oggi.") <<
"\n";
767 Glib::signal_idle().connect_once([
this, alive, txt = oss.str()]() {
768 BastionGuard::if_alive(alive, [this, &txt]() {
769 txt_processes_.get_buffer()->set_text(txt);
770 lbl_status_.set_text(_(
"✅ Aggiornamento completato."));
774 }
catch (
const std::exception &e) {
775 std::string err = std::string(
_(
"Errore update_process_list: ")) + e.what();
784 std::thread([msg]() {
786 std::string base_dir = Glib::get_home_dir() +
"/.local/share/BastionGuard/logs";
787 if (!fs::exists(base_dir))
788 fs::create_directories(base_dir);
791 auto t = std::time(
nullptr);
793 localtime_r(&t, &tm);
795 std::strftime(datebuf,
sizeof(datebuf),
"%Y-%m-%d", &tm);
796 std::string log_path = base_dir +
"/privacyd_" + std::string(datebuf) +
".log";
799 std::ofstream log(log_path, std::ios::app);
802 std::strftime(timebuf,
sizeof(timebuf),
"%H:%M:%S", &tm);
803 log <<
"[" << timebuf <<
"] " << msg << std::endl;
807 const auto now = std::chrono::system_clock::now();
808 const auto seven_days = std::chrono::hours(24 * 7);
810 for (
const auto& entry : fs::directory_iterator(base_dir)) {
811 if (entry.is_regular_file()) {
812 auto ftime = fs::last_write_time(entry);
813 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
814 ftime - fs::file_time_type::clock::now()
815 + std::chrono::system_clock::now());
816 auto age = now - sctp;
817 if (age > seven_days) {
819 fs::remove(entry.path());
828 std::cerr <<
_(
"[Privacy] Errore scrittura log giornaliero.") << std::endl;
837 auto keyfile = Glib::KeyFile::create();
838 auto config_dir = Glib::get_home_dir() +
"/.config/BastionGuard";
839 if (!std::filesystem::exists(config_dir))
840 std::filesystem::create_directories(config_dir);
842 std::string config_path = config_dir +
"/privacy.conf";
843 keyfile->set_boolean(
"privacy",
"enabled", enabled);
844 keyfile->save_to_file(config_path);
845 write_log(enabled ?
_(
"Privacy abilitata") :
_(
"Privacy disabilitata"));
846 }
catch (
const Glib::Error &e) {
847 std::cerr <<
_(
"[Privacy] Errore salvataggio config: ") << e.what() << std::endl;
static bool run_privhelper_pkexec(const std::string &action)
static std::string read_exe_path(const std::string &pid_str)
std::queue< std::string > alert_queue_
static std::string read_cmdline(const std::string &path)
static int read_ppid(const std::string &pid_str)
static bool parent_is_noise(const std::string &pid_str)
static bool is_noise_process(const std::string &comm, const std::string &exe, const std::string &cmdline)
static std::string read_text_file(const std::string &path)
Gtk::Window * current_alert_
std::atomic< bool > toggle_busy_
BastionGuard::AliveFlag alive_
bool load_privacy_state()
std::atomic< bool > devices_blocked_
void start_monitor_thread()
Gtk::TextView txt_processes_
void show_permission_popup(const std::string &process_name)
void save_privacy_state(bool enabled)
Gtk::Switch switch_webcam_
sigc::connection auto_refresh_conn_
void run_privacy_script_async(bool unblock)
Gtk::ScrolledWindow scroller_
void unblock_devices_local()
void update_process_list()
std::atomic< bool > monitor_running_
std::vector< std::string > detect_active_devices()
void write_log(const std::string &msg)
std::thread monitor_thread_
std::set< std::string > known_processes_
void on_refresh_clicked()
void start_privacy_daemon()
Gtk::CheckButton chk_auto_refresh_
void setup_auto_refresh_toggle()
void manual_unlock_devices()
Result run(const std::vector< std::string > &args, bool merge_stderr=false, std::size_t limit=8U *1024U *1024U, const std::map< std::string, std::string > &environment={}, const std::function< bool(const std::string &)> &on_line={})