#include "MainWindow.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; // The initialization list now perfectly matches the header declaration order MainWindow::MainWindow() : m_HeaderBar(), m_MainLayout(Gtk::ORIENTATION_VERTICAL, 0), m_Notebook(), m_ScrolledWindow(), m_LogView(), m_RefTextBuffer(), m_StatusLabel("Ready."), m_VBoxBackup(Gtk::ORIENTATION_VERTICAL, 10), m_LabelBackupInstruction("Select what to backup:"), m_CheckThemesBackup("GTK Themes (~/.themes)"), m_CheckIconsBackup("Icons (~/.icons)"), m_CheckWallpapersBackup("Wallpapers (Dynamic)"), m_CheckExtensionsBackup("GNOME Extensions"), m_CheckDconfBackup("GNOME Settings (dconf)"), m_ButtonBackup("Start Backup"), m_VBoxRestore(Gtk::ORIENTATION_VERTICAL, 10), m_LabelRestoreInstruction("Select what to restore:"), m_CheckThemesRestore("GTK Themes"), m_CheckIconsRestore("Icons"), m_CheckWallpapersRestore("Wallpapers"), m_CheckExtensionsRestore("GNOME Extensions"), m_CheckDconfRestore("GNOME Settings"), m_CheckDryRunRestore("Dry Run (Simulate only, do not write files)"), m_ButtonRestore("Start Restore"), m_WorkerRunning(false) { // Connect the inter-thread dispatcher m_Dispatcher.connect(sigc::mem_fun(*this, &MainWindow::on_dispatcher_ping)); const char* home = std::getenv("HOME"); if (home) { std::string log_path = std::string(home) + "/gnome-vault.log"; m_LogFile.open(log_path, std::ios::app); } set_default_size(650, 700); set_border_width(0); m_HeaderBar.set_title("Gnome-Vault"); m_HeaderBar.set_subtitle("System State Manager"); m_HeaderBar.set_show_close_button(true); set_titlebar(m_HeaderBar); m_LabelBackupInstruction.set_use_markup(true); m_LabelRestoreInstruction.set_use_markup(true); m_RefTextBuffer = Gtk::TextBuffer::create(); m_LogView.set_buffer(m_RefTextBuffer); m_LogView.set_editable(false); m_LogView.set_cursor_visible(false); m_LogView.set_wrap_mode(Gtk::WRAP_WORD); m_LogView.override_background_color(Gdk::RGBA("#1e1e1e")); m_LogView.override_color(Gdk::RGBA("#dcdcdc")); m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); m_ScrolledWindow.set_shadow_type(Gtk::SHADOW_IN); m_ScrolledWindow.add(m_LogView); m_ScrolledWindow.set_size_request(-1, 250); m_ScrolledWindow.set_margin_left(10); m_ScrolledWindow.set_margin_right(10); m_VBoxBackup.set_border_width(15); m_VBoxBackup.pack_start(m_LabelBackupInstruction, Gtk::PACK_SHRINK); m_VBoxBackup.pack_start(m_CheckThemesBackup, Gtk::PACK_SHRINK); m_VBoxBackup.pack_start(m_CheckIconsBackup, Gtk::PACK_SHRINK); m_VBoxBackup.pack_start(m_CheckWallpapersBackup, Gtk::PACK_SHRINK); m_VBoxBackup.pack_start(m_CheckExtensionsBackup, Gtk::PACK_SHRINK); m_VBoxBackup.pack_start(m_CheckDconfBackup, Gtk::PACK_SHRINK); m_CheckThemesBackup.set_active(true); m_CheckIconsBackup.set_active(true); m_CheckWallpapersBackup.set_active(true); m_CheckExtensionsBackup.set_active(true); m_CheckDconfBackup.set_active(true); m_ButtonBackup.set_size_request(-1, 45); m_VBoxBackup.pack_end(m_ButtonBackup, Gtk::PACK_SHRINK); m_VBoxRestore.set_border_width(15); m_VBoxRestore.pack_start(m_LabelRestoreInstruction, Gtk::PACK_SHRINK); m_VBoxRestore.pack_start(m_CheckThemesRestore, Gtk::PACK_SHRINK); m_VBoxRestore.pack_start(m_CheckIconsRestore, Gtk::PACK_SHRINK); m_VBoxRestore.pack_start(m_CheckWallpapersRestore, Gtk::PACK_SHRINK); m_VBoxRestore.pack_start(m_CheckExtensionsRestore, Gtk::PACK_SHRINK); m_VBoxRestore.pack_start(m_CheckDconfRestore, Gtk::PACK_SHRINK); m_CheckThemesRestore.set_active(true); m_CheckIconsRestore.set_active(true); m_CheckWallpapersRestore.set_active(true); m_CheckExtensionsRestore.set_active(true); m_CheckDconfRestore.set_active(true); m_CheckDryRunRestore.set_active(true); m_VBoxRestore.pack_start(m_CheckDryRunRestore, Gtk::PACK_SHRINK); m_ButtonRestore.set_size_request(-1, 45); m_VBoxRestore.pack_end(m_ButtonRestore, Gtk::PACK_SHRINK); m_Notebook.append_page(m_VBoxBackup, "Backup"); m_Notebook.append_page(m_VBoxRestore, "Restore"); m_MainLayout.pack_start(m_Notebook, Gtk::PACK_EXPAND_WIDGET); m_MainLayout.pack_start(m_ScrolledWindow, Gtk::PACK_EXPAND_WIDGET); m_MainLayout.pack_start(m_StatusLabel, Gtk::PACK_SHRINK); m_ButtonBackup.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::on_button_backup_clicked)); m_ButtonRestore.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::on_button_restore_clicked)); add(m_MainLayout); show_all_children(); add_log_ui("Session Started. Multithreaded Engine Ready."); } MainWindow::~MainWindow() { if (m_WorkerRunning) { add_log_ui("Warning: Waiting for background worker to finish before closing..."); while (m_WorkerRunning) std::this_thread::sleep_for(std::chrono::milliseconds(100)); } if (m_LogFile.is_open()) m_LogFile.close(); } void MainWindow::queue_log(const std::string& message) { bool needs_emit = false; { std::lock_guard lock(m_LogMutex); needs_emit = m_LogQueue.empty(); m_LogQueue.push(message); } if (needs_emit) { m_Dispatcher.emit(); } } void MainWindow::on_dispatcher_ping() { std::queue local_queue; { std::lock_guard lock(m_LogMutex); std::swap(local_queue, m_LogQueue); } while (!local_queue.empty()) { std::string msg = local_queue.front(); local_queue.pop(); if (msg == "__UNLOCK_UI__") { set_ui_locked(false); m_StatusLabel.set_text("Operation completed safely."); } else { add_log_ui(msg); } } } void MainWindow::add_log_ui(const std::string& message) { auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); std::stringstream ss; ss << std::put_time(std::localtime(&now), "[%H:%M:%S] ") << message << "\n"; std::string formatted = ss.str(); m_RefTextBuffer->insert(m_RefTextBuffer->end(), formatted); Gtk::TextBuffer::iterator iter = m_RefTextBuffer->end(); m_RefTextBuffer->place_cursor(iter); // THE FIX: Do not attempt to scroll if the window isn't painted on the screen yet! // This stops GTK from segfaulting when trying to calculate geometries in the void. if (m_LogView.get_realized()) { m_LogView.scroll_to(m_RefTextBuffer->get_insert()); } if (m_LogFile.is_open()) { m_LogFile << formatted; m_LogFile.flush(); } } void MainWindow::set_ui_locked(bool locked) { m_ButtonBackup.set_sensitive(!locked); m_ButtonRestore.set_sensitive(!locked); } std::string MainWindow::get_wallpaper_directory() { char buffer[256]; std::string result = ""; FILE* pipe = popen("gsettings get org.gnome.desktop.background picture-uri-dark 2>/dev/null", "r"); if (pipe) { while (fgets(buffer, sizeof(buffer), pipe) != nullptr) result += buffer; pclose(pipe); } size_t file_pos = result.find("file://"); if (file_pos != std::string::npos) { std::string path = result.substr(file_pos + 7); path.erase(std::remove(path.begin(), path.end(), '\''), path.end()); path.erase(std::remove(path.begin(), path.end(), '\n'), path.end()); return fs::path(path).parent_path().string(); } return ""; } bool MainWindow::create_tar_archive(const std::string& source_dir, const std::string& out_filename) { struct archive *a; struct archive *disk; struct archive_entry *entry; char buff[8192]; int len, fd; a = archive_write_new(); archive_write_add_filter_gzip(a); archive_write_set_format_pax_restricted(a); archive_write_open_filename(a, out_filename.c_str()); disk = archive_read_disk_new(); archive_read_disk_set_standard_lookup(disk); fs::directory_options options = fs::directory_options::skip_permission_denied; for (const auto& dirEntry : fs::recursive_directory_iterator(source_dir, options)) { std::string path = dirEntry.path().string(); entry = archive_entry_new(); archive_entry_copy_pathname(entry, path.c_str()); archive_read_disk_entry_from_file(disk, entry, -1, nullptr); archive_write_header(a, entry); queue_log(" -> Packing: " + path); if (fs::is_regular_file(dirEntry)) { fd = ::open(path.c_str(), O_RDONLY); if (fd >= 0) { while ((len = ::read(fd, buff, sizeof(buff))) > 0) { archive_write_data(a, buff, len); } ::close(fd); } } archive_entry_free(entry); } archive_read_free(disk); archive_write_close(a); archive_write_free(a); return true; } bool MainWindow::extract_tar_archive(const std::string& archive_path, bool dry_run) { struct archive *a; struct archive_entry *entry; int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_UNLINK | ARCHIVE_EXTRACT_SECURE_NODOTDOT; a = archive_read_new(); archive_read_support_format_all(a); archive_read_support_filter_all(a); if (archive_read_open_filename(a, archive_path.c_str(), 10240) != ARCHIVE_OK) { queue_log("Error: Failed to open archive " + archive_path); archive_read_free(a); return false; } while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { std::string current_path = archive_entry_pathname(entry); if (!current_path.empty() && current_path[0] != '/') { current_path = "/" + current_path; archive_entry_set_pathname(entry, current_path.c_str()); } if (dry_run) { queue_log(" -> [DRY RUN] Would extract: " + current_path); archive_read_data_skip(a); } else { queue_log(" -> Extracting: " + current_path); if (archive_read_extract(a, entry, flags) != ARCHIVE_OK) { queue_log(" -> Warning: Failed to extract " + current_path + " (" + archive_error_string(a) + ")"); } } } archive_read_close(a); archive_read_free(a); return true; } void MainWindow::run_backup_job(JobConfig config, std::string home_dir, std::string wallpaper_dir) { queue_log("--- Starting Backup Sequence (Worker Thread) ---"); if (config.themes) { std::string target = home_dir + "/.themes"; if (fs::exists(target)) { queue_log("Backing up Themes..."); create_tar_archive(target, home_dir + "/gnome_themes_backup.tar.gz"); } } if (config.icons) { std::string target = home_dir + "/.icons"; if (fs::exists(target)) { queue_log("Backing up Icons..."); create_tar_archive(target, home_dir + "/gnome_icons_backup.tar.gz"); } } if (config.wallpapers) { if (!wallpaper_dir.empty() && wallpaper_dir != home_dir && wallpaper_dir != "/") { queue_log("Backing up Wallpapers from: " + wallpaper_dir); create_tar_archive(wallpaper_dir, home_dir + "/gnome_wallpapers_backup.tar.gz"); } else { queue_log("Warning: Wallpaper directory too broad or empty. Skipped."); } } if (config.extensions) { std::string target = home_dir + "/.local/share/gnome-shell/extensions"; if (fs::exists(target)) { queue_log("Backing up Extensions..."); create_tar_archive(target, home_dir + "/gnome_extensions_backup.tar.gz"); } } if (config.dconf) { queue_log("Dumping dconf settings..."); std::string cmd = "dconf dump / > " + home_dir + "/gnome_dconf_backup.ini"; if (system(cmd.c_str()) == 0) { queue_log(" -> Success: Dumped dconf"); } else { queue_log(" -> Error: dconf dump failed"); } } queue_log("--- Backup Completed ---"); queue_log("__UNLOCK_UI__"); m_WorkerRunning = false; } void MainWindow::on_button_backup_clicked() { if (m_WorkerRunning) return; set_ui_locked(true); m_WorkerRunning = true; m_StatusLabel.set_text("Backup in progress..."); const char* env_home = std::getenv("HOME"); std::string home_dir = env_home ? env_home : ""; if (home_dir.empty()) { add_log_ui("Error: HOME environment variable missing."); set_ui_locked(false); m_WorkerRunning = false; return; } JobConfig config; config.themes = m_CheckThemesBackup.get_active(); config.icons = m_CheckIconsBackup.get_active(); config.wallpapers = m_CheckWallpapersBackup.get_active(); config.extensions = m_CheckExtensionsBackup.get_active(); config.dconf = m_CheckDconfBackup.get_active(); config.dry_run = false; std::string wallpaper_dir = get_wallpaper_directory(); std::thread(&MainWindow::run_backup_job, this, config, home_dir, wallpaper_dir).detach(); } void MainWindow::run_restore_job(JobConfig config, std::string home_dir) { if (config.dry_run) { queue_log("--- Starting DRY RUN Restore (Worker Thread) ---"); queue_log("NO FILES WILL BE MODIFIED"); } else { queue_log("--- Starting LIVE Restore (Worker Thread) ---"); } if (config.themes) { std::string in = home_dir + "/gnome_themes_backup.tar.gz"; if (fs::exists(in)) { queue_log("Processing Themes archive..."); extract_tar_archive(in, config.dry_run); } } if (config.icons) { std::string in = home_dir + "/gnome_icons_backup.tar.gz"; if (fs::exists(in)) { queue_log("Processing Icons archive..."); extract_tar_archive(in, config.dry_run); } } if (config.wallpapers) { std::string in = home_dir + "/gnome_wallpapers_backup.tar.gz"; if (fs::exists(in)) { queue_log("Processing Wallpapers archive..."); extract_tar_archive(in, config.dry_run); } } if (config.extensions) { std::string in = home_dir + "/gnome_extensions_backup.tar.gz"; if (fs::exists(in)) { queue_log("Processing Extensions archive..."); extract_tar_archive(in, config.dry_run); } } if (config.dconf) { std::string in = home_dir + "/gnome_dconf_backup.ini"; if (fs::exists(in)) { if (config.dry_run) { queue_log(" -> [DRY RUN] Would load dconf settings from " + in); } else { queue_log("Injecting dconf settings..."); std::string cmd = "dconf load / < " + in; if (system(cmd.c_str()) == 0) { queue_log(" -> Success: dconf loaded"); } else { queue_log(" -> Error: dconf load failed"); } } } } queue_log("--- Restore Completed ---"); queue_log("__UNLOCK_UI__"); m_WorkerRunning = false; } void MainWindow::on_button_restore_clicked() { if (m_WorkerRunning) return; set_ui_locked(true); m_WorkerRunning = true; m_StatusLabel.set_text("Restore in progress..."); const char* env_home = std::getenv("HOME"); std::string home_dir = env_home ? env_home : ""; if (home_dir.empty()) { add_log_ui("Error: HOME environment variable missing."); set_ui_locked(false); m_WorkerRunning = false; return; } JobConfig config; config.themes = m_CheckThemesRestore.get_active(); config.icons = m_CheckIconsRestore.get_active(); config.wallpapers = m_CheckWallpapersRestore.get_active(); config.extensions = m_CheckExtensionsRestore.get_active(); config.dconf = m_CheckDconfRestore.get_active(); config.dry_run = m_CheckDryRunRestore.get_active(); std::thread(&MainWindow::run_restore_job, this, config, home_dir).detach(); }