MainWindow.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. #include "MainWindow.hpp"
  2. #include <iostream>
  3. #include <filesystem>
  4. #include <archive.h>
  5. #include <archive_entry.h>
  6. #include <fcntl.h>
  7. #include <unistd.h>
  8. #include <cstdlib>
  9. #include <cstdio>
  10. #include <algorithm>
  11. #include <chrono>
  12. #include <iomanip>
  13. #include <sstream>
  14. namespace fs = std::filesystem;
  15. // The initialization list now perfectly matches the header declaration order
  16. MainWindow::MainWindow()
  17. : m_HeaderBar(),
  18. m_MainLayout(Gtk::ORIENTATION_VERTICAL, 0),
  19. m_Notebook(),
  20. m_ScrolledWindow(),
  21. m_LogView(),
  22. m_RefTextBuffer(),
  23. m_StatusLabel("Ready."),
  24. m_VBoxBackup(Gtk::ORIENTATION_VERTICAL, 10),
  25. m_LabelBackupInstruction("<span size='large' weight='bold'>Select what to backup:</span>"),
  26. m_CheckThemesBackup("GTK Themes (~/.themes)"),
  27. m_CheckIconsBackup("Icons (~/.icons)"),
  28. m_CheckWallpapersBackup("Wallpapers (Dynamic)"),
  29. m_CheckExtensionsBackup("GNOME Extensions"),
  30. m_CheckDconfBackup("GNOME Settings (dconf)"),
  31. m_ButtonBackup("Start Backup"),
  32. m_VBoxRestore(Gtk::ORIENTATION_VERTICAL, 10),
  33. m_LabelRestoreInstruction("<span size='large' weight='bold'>Select what to restore:</span>"),
  34. m_CheckThemesRestore("GTK Themes"),
  35. m_CheckIconsRestore("Icons"),
  36. m_CheckWallpapersRestore("Wallpapers"),
  37. m_CheckExtensionsRestore("GNOME Extensions"),
  38. m_CheckDconfRestore("GNOME Settings"),
  39. m_CheckDryRunRestore("Dry Run (Simulate only, do not write files)"),
  40. m_ButtonRestore("Start Restore"),
  41. m_WorkerRunning(false)
  42. {
  43. // Connect the inter-thread dispatcher
  44. m_Dispatcher.connect(sigc::mem_fun(*this, &MainWindow::on_dispatcher_ping));
  45. const char* home = std::getenv("HOME");
  46. if (home) {
  47. std::string log_path = std::string(home) + "/gnome-vault.log";
  48. m_LogFile.open(log_path, std::ios::app);
  49. }
  50. set_default_size(650, 700);
  51. set_border_width(0);
  52. m_HeaderBar.set_title("Gnome-Vault");
  53. m_HeaderBar.set_subtitle("System State Manager");
  54. m_HeaderBar.set_show_close_button(true);
  55. set_titlebar(m_HeaderBar);
  56. m_LabelBackupInstruction.set_use_markup(true);
  57. m_LabelRestoreInstruction.set_use_markup(true);
  58. m_RefTextBuffer = Gtk::TextBuffer::create();
  59. m_LogView.set_buffer(m_RefTextBuffer);
  60. m_LogView.set_editable(false);
  61. m_LogView.set_cursor_visible(false);
  62. m_LogView.set_wrap_mode(Gtk::WRAP_WORD);
  63. m_LogView.override_background_color(Gdk::RGBA("#1e1e1e"));
  64. m_LogView.override_color(Gdk::RGBA("#dcdcdc"));
  65. m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  66. m_ScrolledWindow.set_shadow_type(Gtk::SHADOW_IN);
  67. m_ScrolledWindow.add(m_LogView);
  68. m_ScrolledWindow.set_size_request(-1, 250);
  69. m_ScrolledWindow.set_margin_left(10);
  70. m_ScrolledWindow.set_margin_right(10);
  71. m_VBoxBackup.set_border_width(15);
  72. m_VBoxBackup.pack_start(m_LabelBackupInstruction, Gtk::PACK_SHRINK);
  73. m_VBoxBackup.pack_start(m_CheckThemesBackup, Gtk::PACK_SHRINK);
  74. m_VBoxBackup.pack_start(m_CheckIconsBackup, Gtk::PACK_SHRINK);
  75. m_VBoxBackup.pack_start(m_CheckWallpapersBackup, Gtk::PACK_SHRINK);
  76. m_VBoxBackup.pack_start(m_CheckExtensionsBackup, Gtk::PACK_SHRINK);
  77. m_VBoxBackup.pack_start(m_CheckDconfBackup, Gtk::PACK_SHRINK);
  78. m_CheckThemesBackup.set_active(true);
  79. m_CheckIconsBackup.set_active(true);
  80. m_CheckWallpapersBackup.set_active(true);
  81. m_CheckExtensionsBackup.set_active(true);
  82. m_CheckDconfBackup.set_active(true);
  83. m_ButtonBackup.set_size_request(-1, 45);
  84. m_VBoxBackup.pack_end(m_ButtonBackup, Gtk::PACK_SHRINK);
  85. m_VBoxRestore.set_border_width(15);
  86. m_VBoxRestore.pack_start(m_LabelRestoreInstruction, Gtk::PACK_SHRINK);
  87. m_VBoxRestore.pack_start(m_CheckThemesRestore, Gtk::PACK_SHRINK);
  88. m_VBoxRestore.pack_start(m_CheckIconsRestore, Gtk::PACK_SHRINK);
  89. m_VBoxRestore.pack_start(m_CheckWallpapersRestore, Gtk::PACK_SHRINK);
  90. m_VBoxRestore.pack_start(m_CheckExtensionsRestore, Gtk::PACK_SHRINK);
  91. m_VBoxRestore.pack_start(m_CheckDconfRestore, Gtk::PACK_SHRINK);
  92. m_CheckThemesRestore.set_active(true);
  93. m_CheckIconsRestore.set_active(true);
  94. m_CheckWallpapersRestore.set_active(true);
  95. m_CheckExtensionsRestore.set_active(true);
  96. m_CheckDconfRestore.set_active(true);
  97. m_CheckDryRunRestore.set_active(true);
  98. m_VBoxRestore.pack_start(m_CheckDryRunRestore, Gtk::PACK_SHRINK);
  99. m_ButtonRestore.set_size_request(-1, 45);
  100. m_VBoxRestore.pack_end(m_ButtonRestore, Gtk::PACK_SHRINK);
  101. m_Notebook.append_page(m_VBoxBackup, "Backup");
  102. m_Notebook.append_page(m_VBoxRestore, "Restore");
  103. m_MainLayout.pack_start(m_Notebook, Gtk::PACK_EXPAND_WIDGET);
  104. m_MainLayout.pack_start(m_ScrolledWindow, Gtk::PACK_EXPAND_WIDGET);
  105. m_MainLayout.pack_start(m_StatusLabel, Gtk::PACK_SHRINK);
  106. m_ButtonBackup.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::on_button_backup_clicked));
  107. m_ButtonRestore.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::on_button_restore_clicked));
  108. add(m_MainLayout);
  109. show_all_children();
  110. add_log_ui("Session Started. Multithreaded Engine Ready.");
  111. }
  112. MainWindow::~MainWindow() {
  113. if (m_WorkerRunning) {
  114. add_log_ui("Warning: Waiting for background worker to finish before closing...");
  115. while (m_WorkerRunning) std::this_thread::sleep_for(std::chrono::milliseconds(100));
  116. }
  117. if (m_LogFile.is_open()) m_LogFile.close();
  118. }
  119. void MainWindow::queue_log(const std::string& message) {
  120. bool needs_emit = false;
  121. {
  122. std::lock_guard<std::mutex> lock(m_LogMutex);
  123. needs_emit = m_LogQueue.empty();
  124. m_LogQueue.push(message);
  125. }
  126. if (needs_emit) {
  127. m_Dispatcher.emit();
  128. }
  129. }
  130. void MainWindow::on_dispatcher_ping() {
  131. std::queue<std::string> local_queue;
  132. {
  133. std::lock_guard<std::mutex> lock(m_LogMutex);
  134. std::swap(local_queue, m_LogQueue);
  135. }
  136. while (!local_queue.empty()) {
  137. std::string msg = local_queue.front();
  138. local_queue.pop();
  139. if (msg == "__UNLOCK_UI__") {
  140. set_ui_locked(false);
  141. m_StatusLabel.set_text("Operation completed safely.");
  142. } else {
  143. add_log_ui(msg);
  144. }
  145. }
  146. }
  147. void MainWindow::add_log_ui(const std::string& message) {
  148. auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
  149. std::stringstream ss;
  150. ss << std::put_time(std::localtime(&now), "[%H:%M:%S] ") << message << "\n";
  151. std::string formatted = ss.str();
  152. m_RefTextBuffer->insert(m_RefTextBuffer->end(), formatted);
  153. Gtk::TextBuffer::iterator iter = m_RefTextBuffer->end();
  154. m_RefTextBuffer->place_cursor(iter);
  155. // THE FIX: Do not attempt to scroll if the window isn't painted on the screen yet!
  156. // This stops GTK from segfaulting when trying to calculate geometries in the void.
  157. if (m_LogView.get_realized()) {
  158. m_LogView.scroll_to(m_RefTextBuffer->get_insert());
  159. }
  160. if (m_LogFile.is_open()) {
  161. m_LogFile << formatted;
  162. m_LogFile.flush();
  163. }
  164. }
  165. void MainWindow::set_ui_locked(bool locked) {
  166. m_ButtonBackup.set_sensitive(!locked);
  167. m_ButtonRestore.set_sensitive(!locked);
  168. }
  169. std::string MainWindow::get_wallpaper_directory() {
  170. char buffer[256];
  171. std::string result = "";
  172. FILE* pipe = popen("gsettings get org.gnome.desktop.background picture-uri-dark 2>/dev/null", "r");
  173. if (pipe) {
  174. while (fgets(buffer, sizeof(buffer), pipe) != nullptr) result += buffer;
  175. pclose(pipe);
  176. }
  177. size_t file_pos = result.find("file://");
  178. if (file_pos != std::string::npos) {
  179. std::string path = result.substr(file_pos + 7);
  180. path.erase(std::remove(path.begin(), path.end(), '\''), path.end());
  181. path.erase(std::remove(path.begin(), path.end(), '\n'), path.end());
  182. return fs::path(path).parent_path().string();
  183. }
  184. return "";
  185. }
  186. bool MainWindow::create_tar_archive(const std::string& source_dir, const std::string& out_filename) {
  187. struct archive *a;
  188. struct archive *disk;
  189. struct archive_entry *entry;
  190. char buff[8192];
  191. int len, fd;
  192. a = archive_write_new();
  193. archive_write_add_filter_gzip(a);
  194. archive_write_set_format_pax_restricted(a);
  195. archive_write_open_filename(a, out_filename.c_str());
  196. disk = archive_read_disk_new();
  197. archive_read_disk_set_standard_lookup(disk);
  198. fs::directory_options options = fs::directory_options::skip_permission_denied;
  199. for (const auto& dirEntry : fs::recursive_directory_iterator(source_dir, options)) {
  200. std::string path = dirEntry.path().string();
  201. entry = archive_entry_new();
  202. archive_entry_copy_pathname(entry, path.c_str());
  203. archive_read_disk_entry_from_file(disk, entry, -1, nullptr);
  204. archive_write_header(a, entry);
  205. queue_log(" -> Packing: " + path);
  206. if (fs::is_regular_file(dirEntry)) {
  207. fd = ::open(path.c_str(), O_RDONLY);
  208. if (fd >= 0) {
  209. while ((len = ::read(fd, buff, sizeof(buff))) > 0) {
  210. archive_write_data(a, buff, len);
  211. }
  212. ::close(fd);
  213. }
  214. }
  215. archive_entry_free(entry);
  216. }
  217. archive_read_free(disk);
  218. archive_write_close(a);
  219. archive_write_free(a);
  220. return true;
  221. }
  222. bool MainWindow::extract_tar_archive(const std::string& archive_path, bool dry_run) {
  223. struct archive *a;
  224. struct archive_entry *entry;
  225. int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_UNLINK | ARCHIVE_EXTRACT_SECURE_NODOTDOT;
  226. a = archive_read_new();
  227. archive_read_support_format_all(a);
  228. archive_read_support_filter_all(a);
  229. if (archive_read_open_filename(a, archive_path.c_str(), 10240) != ARCHIVE_OK) {
  230. queue_log("Error: Failed to open archive " + archive_path);
  231. archive_read_free(a);
  232. return false;
  233. }
  234. while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
  235. std::string current_path = archive_entry_pathname(entry);
  236. if (!current_path.empty() && current_path[0] != '/') {
  237. current_path = "/" + current_path;
  238. archive_entry_set_pathname(entry, current_path.c_str());
  239. }
  240. if (dry_run) {
  241. queue_log(" -> [DRY RUN] Would extract: " + current_path);
  242. archive_read_data_skip(a);
  243. } else {
  244. queue_log(" -> Extracting: " + current_path);
  245. if (archive_read_extract(a, entry, flags) != ARCHIVE_OK) {
  246. queue_log(" -> Warning: Failed to extract " + current_path + " (" + archive_error_string(a) + ")");
  247. }
  248. }
  249. }
  250. archive_read_close(a);
  251. archive_read_free(a);
  252. return true;
  253. }
  254. void MainWindow::run_backup_job(JobConfig config, std::string home_dir, std::string wallpaper_dir) {
  255. queue_log("--- Starting Backup Sequence (Worker Thread) ---");
  256. if (config.themes) {
  257. std::string target = home_dir + "/.themes";
  258. if (fs::exists(target)) {
  259. queue_log("Backing up Themes...");
  260. create_tar_archive(target, home_dir + "/gnome_themes_backup.tar.gz");
  261. }
  262. }
  263. if (config.icons) {
  264. std::string target = home_dir + "/.icons";
  265. if (fs::exists(target)) {
  266. queue_log("Backing up Icons...");
  267. create_tar_archive(target, home_dir + "/gnome_icons_backup.tar.gz");
  268. }
  269. }
  270. if (config.wallpapers) {
  271. if (!wallpaper_dir.empty() && wallpaper_dir != home_dir && wallpaper_dir != "/") {
  272. queue_log("Backing up Wallpapers from: " + wallpaper_dir);
  273. create_tar_archive(wallpaper_dir, home_dir + "/gnome_wallpapers_backup.tar.gz");
  274. } else {
  275. queue_log("Warning: Wallpaper directory too broad or empty. Skipped.");
  276. }
  277. }
  278. if (config.extensions) {
  279. std::string target = home_dir + "/.local/share/gnome-shell/extensions";
  280. if (fs::exists(target)) {
  281. queue_log("Backing up Extensions...");
  282. create_tar_archive(target, home_dir + "/gnome_extensions_backup.tar.gz");
  283. }
  284. }
  285. if (config.dconf) {
  286. queue_log("Dumping dconf settings...");
  287. std::string cmd = "dconf dump / > " + home_dir + "/gnome_dconf_backup.ini";
  288. if (system(cmd.c_str()) == 0) {
  289. queue_log(" -> Success: Dumped dconf");
  290. } else {
  291. queue_log(" -> Error: dconf dump failed");
  292. }
  293. }
  294. queue_log("--- Backup Completed ---");
  295. queue_log("__UNLOCK_UI__");
  296. m_WorkerRunning = false;
  297. }
  298. void MainWindow::on_button_backup_clicked() {
  299. if (m_WorkerRunning) return;
  300. set_ui_locked(true);
  301. m_WorkerRunning = true;
  302. m_StatusLabel.set_text("Backup in progress...");
  303. const char* env_home = std::getenv("HOME");
  304. std::string home_dir = env_home ? env_home : "";
  305. if (home_dir.empty()) {
  306. add_log_ui("Error: HOME environment variable missing.");
  307. set_ui_locked(false);
  308. m_WorkerRunning = false;
  309. return;
  310. }
  311. JobConfig config;
  312. config.themes = m_CheckThemesBackup.get_active();
  313. config.icons = m_CheckIconsBackup.get_active();
  314. config.wallpapers = m_CheckWallpapersBackup.get_active();
  315. config.extensions = m_CheckExtensionsBackup.get_active();
  316. config.dconf = m_CheckDconfBackup.get_active();
  317. config.dry_run = false;
  318. std::string wallpaper_dir = get_wallpaper_directory();
  319. std::thread(&MainWindow::run_backup_job, this, config, home_dir, wallpaper_dir).detach();
  320. }
  321. void MainWindow::run_restore_job(JobConfig config, std::string home_dir) {
  322. if (config.dry_run) {
  323. queue_log("--- Starting DRY RUN Restore (Worker Thread) ---");
  324. queue_log("NO FILES WILL BE MODIFIED");
  325. } else {
  326. queue_log("--- Starting LIVE Restore (Worker Thread) ---");
  327. }
  328. if (config.themes) {
  329. std::string in = home_dir + "/gnome_themes_backup.tar.gz";
  330. if (fs::exists(in)) {
  331. queue_log("Processing Themes archive...");
  332. extract_tar_archive(in, config.dry_run);
  333. }
  334. }
  335. if (config.icons) {
  336. std::string in = home_dir + "/gnome_icons_backup.tar.gz";
  337. if (fs::exists(in)) {
  338. queue_log("Processing Icons archive...");
  339. extract_tar_archive(in, config.dry_run);
  340. }
  341. }
  342. if (config.wallpapers) {
  343. std::string in = home_dir + "/gnome_wallpapers_backup.tar.gz";
  344. if (fs::exists(in)) {
  345. queue_log("Processing Wallpapers archive...");
  346. extract_tar_archive(in, config.dry_run);
  347. }
  348. }
  349. if (config.extensions) {
  350. std::string in = home_dir + "/gnome_extensions_backup.tar.gz";
  351. if (fs::exists(in)) {
  352. queue_log("Processing Extensions archive...");
  353. extract_tar_archive(in, config.dry_run);
  354. }
  355. }
  356. if (config.dconf) {
  357. std::string in = home_dir + "/gnome_dconf_backup.ini";
  358. if (fs::exists(in)) {
  359. if (config.dry_run) {
  360. queue_log(" -> [DRY RUN] Would load dconf settings from " + in);
  361. } else {
  362. queue_log("Injecting dconf settings...");
  363. std::string cmd = "dconf load / < " + in;
  364. if (system(cmd.c_str()) == 0) {
  365. queue_log(" -> Success: dconf loaded");
  366. } else {
  367. queue_log(" -> Error: dconf load failed");
  368. }
  369. }
  370. }
  371. }
  372. queue_log("--- Restore Completed ---");
  373. queue_log("__UNLOCK_UI__");
  374. m_WorkerRunning = false;
  375. }
  376. void MainWindow::on_button_restore_clicked() {
  377. if (m_WorkerRunning) return;
  378. set_ui_locked(true);
  379. m_WorkerRunning = true;
  380. m_StatusLabel.set_text("Restore in progress...");
  381. const char* env_home = std::getenv("HOME");
  382. std::string home_dir = env_home ? env_home : "";
  383. if (home_dir.empty()) {
  384. add_log_ui("Error: HOME environment variable missing.");
  385. set_ui_locked(false);
  386. m_WorkerRunning = false;
  387. return;
  388. }
  389. JobConfig config;
  390. config.themes = m_CheckThemesRestore.get_active();
  391. config.icons = m_CheckIconsRestore.get_active();
  392. config.wallpapers = m_CheckWallpapersRestore.get_active();
  393. config.extensions = m_CheckExtensionsRestore.get_active();
  394. config.dconf = m_CheckDconfRestore.get_active();
  395. config.dry_run = m_CheckDryRunRestore.get_active();
  396. std::thread(&MainWindow::run_restore_job, this, config, home_dir).detach();
  397. }