Nicole Portas vor 2 Wochen
Ursprung
Commit
6aefa1c79a
3 geänderte Dateien mit 441 neuen und 66 gelöschten Zeilen
  1. 362 62
      MainWindow.cpp
  2. 44 0
      MainWindow.hpp
  3. 35 4
      main.cpp

+ 362 - 62
MainWindow.cpp

@@ -6,98 +6,221 @@
 #include <fcntl.h>
 #include <unistd.h>
 #include <cstdlib>
+#include <cstdio>
+#include <algorithm>
+#include <chrono>
+#include <iomanip>
+#include <sstream>
 
 namespace fs = std::filesystem;
 
+// The initialization list now perfectly matches the header declaration order
 MainWindow::MainWindow()
-: m_MainLayout(Gtk::ORIENTATION_VERTICAL, 0),
-  m_VBoxBackup(Gtk::ORIENTATION_VERTICAL, 15),
+: 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("<span size='large' weight='bold'>Select what to backup:</span>"),
   m_CheckThemesBackup("GTK Themes (~/.themes)"),
   m_CheckIconsBackup("Icons (~/.icons)"),
-  m_CheckDconfBackup("GNOME Settings (dconf dump)"),
+  m_CheckWallpapersBackup("Wallpapers (Dynamic)"),
+  m_CheckExtensionsBackup("GNOME Extensions"),
+  m_CheckDconfBackup("GNOME Settings (dconf)"),
   m_ButtonBackup("Start Backup"),
-  m_VBoxRestore(Gtk::ORIENTATION_VERTICAL, 15),
+  m_VBoxRestore(Gtk::ORIENTATION_VERTICAL, 10),
   m_LabelRestoreInstruction("<span size='large' weight='bold'>Select what to restore:</span>"),
   m_CheckThemesRestore("GTK Themes"),
   m_CheckIconsRestore("Icons"),
+  m_CheckWallpapersRestore("Wallpapers"),
+  m_CheckExtensionsRestore("GNOME Extensions"),
   m_CheckDconfRestore("GNOME Settings"),
-  m_ButtonRestore("Start Restore")
+  m_CheckDryRunRestore("Dry Run (Simulate only, do not write files)"),
+  m_ButtonRestore("Start Restore"),
+  m_WorkerRunning(false)
 {
-    // --- Force Dark Theme ---
-    auto settings = Gtk::Settings::get_default();
-    if (settings) {
-        settings->property_gtk_application_prefer_dark_theme() = true;
+    // 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(550, 450);
-    set_border_width(0); // Removing window border to let layout handle it
+    set_default_size(650, 700);
+    set_border_width(0);
 
-    // --- HeaderBar Setup (Modern GNOME look) ---
     m_HeaderBar.set_title("Gnome-Vault");
     m_HeaderBar.set_subtitle("System State Manager");
     m_HeaderBar.set_show_close_button(true);
     set_titlebar(m_HeaderBar);
 
-    // Allow markup in the instruction labels
     m_LabelBackupInstruction.set_use_markup(true);
-    m_LabelBackupInstruction.set_halign(Gtk::ALIGN_START);
     m_LabelRestoreInstruction.set_use_markup(true);
-    m_LabelRestoreInstruction.set_halign(Gtk::ALIGN_START);
 
-    // --- Backup Page Setup ---
-    m_VBoxBackup.set_border_width(20);
+    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);
-
-    // Make the button chunkier
+    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_ButtonBackup.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::on_button_backup_clicked));
     m_VBoxBackup.pack_end(m_ButtonBackup, Gtk::PACK_SHRINK);
 
-    // --- Restore Page Setup ---
-    m_VBoxRestore.set_border_width(20);
+    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_ButtonRestore.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::on_button_restore_clicked));
     m_VBoxRestore.pack_end(m_ButtonRestore, Gtk::PACK_SHRINK);
 
-    // --- Notebook (Tabs) Setup ---
     m_Notebook.append_page(m_VBoxBackup, "Backup");
     m_Notebook.append_page(m_VBoxRestore, "Restore");
 
-    // --- Status Label Setup ---
-    m_StatusLabel.set_text("Ready.");
-    m_StatusLabel.set_halign(Gtk::ALIGN_START);
-    m_StatusLabel.set_margin_top(10);
-    m_StatusLabel.set_margin_bottom(10);
-    m_StatusLabel.set_margin_start(10);
-
-    // --- Main Layout Assembly ---
     m_MainLayout.pack_start(m_Notebook, Gtk::PACK_EXPAND_WIDGET);
-    m_MainLayout.pack_end(m_StatusLabel, Gtk::PACK_SHRINK);
+    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<std::mutex> 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<std::string> local_queue;
+    {
+        std::lock_guard<std::mutex> 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();
+    }
 }
 
-MainWindow::~MainWindow() {}
+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;
-    int fd;
+    int len, fd;
 
     a = archive_write_new();
     archive_write_add_filter_gzip(a);
@@ -107,20 +230,21 @@ bool MainWindow::create_tar_archive(const std::string& source_dir, const std::st
     disk = archive_read_disk_new();
     archive_read_disk_set_standard_lookup(disk);
 
-    for (const auto& dirEntry : fs::recursive_directory_iterator(source_dir)) {
+    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) {
-                len = ::read(fd, buff, sizeof(buff));
-                while (len > 0) {
+                while ((len = ::read(fd, buff, sizeof(buff))) > 0) {
                     archive_write_data(a, buff, len);
-                    len = ::read(fd, buff, sizeof(buff));
                 }
                 ::close(fd);
             }
@@ -133,34 +257,210 @@ bool MainWindow::create_tar_archive(const std::string& source_dir, const std::st
     return true;
 }
 
-void MainWindow::on_button_backup_clicked() {
-    m_StatusLabel.set_text("Initializing Backup Sequence...");
-    // Keep the cout for the terminal debugger
-    std::cout << ">>> Initializing Backup Sequence..." << std::endl; 
-    
-    std::string home_dir = getenv("HOME");
-    
-    if (m_CheckThemesBackup.get_active()) {
+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";
-        std::string out = home_dir + "/gnome_themes_backup.tar.gz";
-        
         if (fs::exists(target)) {
-            m_StatusLabel.set_text("Packing Themes into archive...");
-            create_tar_archive(target, out);
-            m_StatusLabel.set_text("Success: Packed ~/.themes into gnome_themes_backup.tar.gz");
-        } else {
-            m_StatusLabel.set_text("Warning: ~/.themes doesn't exist. Skipped.");
+            queue_log("Backing up Themes...");
+            create_tar_archive(target, home_dir + "/gnome_themes_backup.tar.gz");
         }
     }
     
-    if (m_CheckIconsBackup.get_active()) std::cout << " [x] Queuing Icons (TODO)..." << std::endl;
-    if (m_CheckDconfBackup.get_active()) std::cout << " [x] Queuing Dconf (TODO)..." << std::endl;
+    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() {
-    m_StatusLabel.set_text("Initializing Restore Sequence...");
-    std::cout << ">>> Initializing Restore Sequence..." << std::endl;
-    if (m_CheckThemesRestore.get_active()) std::cout << " [x] Preparing to overwrite Themes..." << std::endl;
-    if (m_CheckIconsRestore.get_active()) std::cout << " [x] Preparing to overwrite Icons..." << std::endl;
-    if (m_CheckDconfRestore.get_active()) std::cout << " [x] Preparing to inject Dconf..." << std::endl;
+    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();
 }

+ 44 - 0
MainWindow.hpp

@@ -1,6 +1,20 @@
 #pragma once
 #include <gtkmm.h>
 #include <string>
+#include <fstream>
+#include <thread>
+#include <mutex>
+#include <queue>
+#include <atomic>
+
+struct JobConfig {
+    bool themes;
+    bool icons;
+    bool wallpapers;
+    bool extensions;
+    bool dconf;
+    bool dry_run;
+};
 
 class MainWindow : public Gtk::Window {
 public:
@@ -12,6 +26,11 @@ private:
     Gtk::HeaderBar m_HeaderBar;
     Gtk::Box m_MainLayout;
     Gtk::Notebook m_Notebook;
+    
+    // Logging UI
+    Gtk::ScrolledWindow m_ScrolledWindow;
+    Gtk::TextView m_LogView;
+    Glib::RefPtr<Gtk::TextBuffer> m_RefTextBuffer;
     Gtk::Label m_StatusLabel;
 
     // --- Backup Page Elements ---
@@ -19,6 +38,8 @@ private:
     Gtk::Label m_LabelBackupInstruction;
     Gtk::CheckButton m_CheckThemesBackup;
     Gtk::CheckButton m_CheckIconsBackup;
+    Gtk::CheckButton m_CheckWallpapersBackup;
+    Gtk::CheckButton m_CheckExtensionsBackup;
     Gtk::CheckButton m_CheckDconfBackup;
     Gtk::Button m_ButtonBackup;
 
@@ -27,11 +48,34 @@ private:
     Gtk::Label m_LabelRestoreInstruction;
     Gtk::CheckButton m_CheckThemesRestore;
     Gtk::CheckButton m_CheckIconsRestore;
+    Gtk::CheckButton m_CheckWallpapersRestore;
+    Gtk::CheckButton m_CheckExtensionsRestore;
     Gtk::CheckButton m_CheckDconfRestore;
+    Gtk::CheckButton m_CheckDryRunRestore;
     Gtk::Button m_ButtonRestore;
 
+    // --- Threading & Concurrency ---
+    Glib::Dispatcher m_Dispatcher;
+    std::mutex m_LogMutex;
+    std::queue<std::string> m_LogQueue;
+    std::atomic<bool> m_WorkerRunning;
+    std::ofstream m_LogFile;
+
     // --- Signal Handlers and Logic ---
     void on_button_backup_clicked();
     void on_button_restore_clicked();
+    
+    // --- UI Thread Functions ---
+    void on_dispatcher_ping();
+    void add_log_ui(const std::string& message);
+    void set_ui_locked(bool locked);
+
+    // --- Worker Thread Functions ---
+    void queue_log(const std::string& message);
+    void run_backup_job(JobConfig config, std::string home_dir, std::string wallpaper_dir);
+    void run_restore_job(JobConfig config, std::string home_dir);
+    
     bool create_tar_archive(const std::string& source_dir, const std::string& out_filename);
+    bool extract_tar_archive(const std::string& archive_path, bool dry_run);
+    std::string get_wallpaper_directory();
 };

+ 35 - 4
main.cpp

@@ -1,9 +1,40 @@
 #include "MainWindow.hpp"
 #include <gtkmm/application.h>
+#include <gtk/gtk.h>
+#include <iostream>
+#include <cstdlib>
+#include <sys/stat.h>
+#include <string>
 
 int main(int argc, char *argv[]) {
-    // org.gnome.vault is our application ID
-    auto app = Gtk::Application::create(argc, argv, "org.gnome.vault");
-    MainWindow window;
-    return app->run(window);
+    // --- THE HIJACK: Force Fontconfig to use a writable /tmp directory ---
+    // This stops Pango from shitting the bed on headless/restrictive servers
+    const char* user = std::getenv("USER");
+    std::string safe_cache_dir = std::string("/tmp/gnome-vault-cache-") + (user ? user : "default");
+    
+    // Create the temporary directory (0777 gives it wide open permissions)
+    mkdir(safe_cache_dir.c_str(), 0777);
+    
+    // Forcibly inject this into the environment before GTK boots
+    setenv("XDG_CACHE_HOME", safe_cache_dir.c_str(), 1);
+
+    // --- Standard GTK Initialization ---
+    if (!gtk_init_check(&argc, &argv)) {
+        std::cerr << "Bloody hell: GTK failed to initialize." << std::endl;
+        std::cerr << "Your X11/Wayland display is either missing or rejecting the connection." << std::endl;
+        std::cerr << "If you are on SSH, ensure you used 'ssh -X' and your local X server is running." << std::endl;
+        return 1;
+    }
+
+    try {
+        auto app = Gtk::Application::create(argc, argv, "org.gnome.vault");
+        MainWindow window;
+        return app->run(window);
+    } catch (const Glib::Error& ex) {
+        std::cerr << "GTK Error: " << ex.what() << std::endl;
+        return 1;
+    } catch (const std::exception& ex) {
+        std::cerr << "Standard Exception: " << ex.what() << std::endl;
+        return 1;
+    }
 }