From 4d528dacec544782035ae13f6afd2e5c195fc8f2 Mon Sep 17 00:00:00 2001 From: fused01 Date: Sun, 23 Nov 2025 15:53:19 +0100 Subject: [PATCH 01/29] Added ui for btrfs subvolume names Save and load config for btrfs subvolume names. Added variables for home and root subvolumes to Main. --- src/Core/Main.vala | 93 ++++++++++++++++++--------------- src/Core/Snapshot.vala | 4 +- src/Core/SnapshotRepo.vala | 20 +++---- src/Core/Subvolume.vala | 8 +-- src/Gtk/RestoreWindow.vala | 2 +- src/Gtk/SnapshotBackendBox.vala | 42 +++++++++++++++ src/Gtk/SnapshotListBox.vala | 16 +++--- 7 files changed, 118 insertions(+), 67 deletions(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 38b6023b..bcabbed8 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -53,6 +53,9 @@ public class Main : GLib.Object{ public bool include_btrfs_home_for_backup = false; public bool include_btrfs_home_for_restore = false; public static bool btrfs_version__can_recursive_delete = false; + + public string root_subvolume_name = "@"; + public string home_subvolume_name = "@home"; public bool stop_cron_emails = true; @@ -486,9 +489,9 @@ public class Main : GLib.Object{ log_debug("check_btrfs_layout_system()"); - bool supported = sys_subvolumes.has_key("@"); + bool supported = sys_subvolumes.has_key(root_subvolume_name); if (include_btrfs_home_for_backup){ - supported = supported && sys_subvolumes.has_key("@home"); + supported = supported && sys_subvolumes.has_key(home_subvolume_name); } if (!supported){ @@ -519,10 +522,10 @@ public class Main : GLib.Object{ if (dev_home != dev_root){ - supported = supported && check_btrfs_volume(dev_root, "@", unlock); + supported = supported && check_btrfs_volume(dev_root, root_subvolume_name, unlock); if (include_btrfs_home_for_backup){ - supported = supported && check_btrfs_volume(dev_home, "@home", unlock); + supported = supported && check_btrfs_volume(dev_home, home_subvolume_name, unlock); } } else{ @@ -530,7 +533,7 @@ public class Main : GLib.Object{ supported = supported && check_btrfs_volume(dev_root, "@,@home", unlock); } else{ - supported = supported && check_btrfs_volume(dev_root, "@", unlock); + supported = supported && check_btrfs_volume(dev_root, root_subvolume_name, unlock); } } } @@ -1718,9 +1721,9 @@ public class Main : GLib.Object{ log_msg(_("Creating new backup...") + "(BTRFS)"); - log_msg(_("Saving to device") + ": %s".printf(repo.device.device) + ", " + _("mounted at path") + ": %s".printf(repo.mount_paths["@"])); + log_msg(_("Saving to device") + ": %s".printf(repo.device.device) + ", " + _("mounted at path") + ": %s".printf(repo.mount_paths[root_subvolume_name])); if ((repo.device_home != null) && (repo.device_home.uuid != repo.device.uuid)){ - log_msg(_("Saving to device") + ": %s".printf(repo.device_home.device) + ", " + _("mounted at path") + ": %s".printf(repo.mount_paths["@home"])); + log_msg(_("Saving to device") + ": %s".printf(repo.device_home.device) + ", " + _("mounted at path") + ": %s".printf(repo.mount_paths[home_subvolume_name])); } // take new backup --------------------------------- @@ -1737,11 +1740,11 @@ public class Main : GLib.Object{ // create subvolume snapshots - var subvol_names = new string[] { "@" }; + var subvol_names = new string[] { root_subvolume_name }; if (include_btrfs_home_for_backup){ - subvol_names = new string[] { "@","@home" }; + subvol_names = new string[] { root_subvolume_name,home_subvolume_name }; } foreach(var subvol_name in subvol_names){ @@ -1784,7 +1787,7 @@ public class Main : GLib.Object{ //log_msg(_("Writing control file...")); - snapshot_path = path_combine(repo.mount_paths["@"], "timeshift-btrfs/snapshots/%s".printf(snapshot_name)); + snapshot_path = path_combine(repo.mount_paths[root_subvolume_name], "timeshift-btrfs/snapshots/%s".printf(snapshot_name)); string initial_tags = (tag == "ondemand") ? "" : tag; @@ -2342,11 +2345,11 @@ public class Main : GLib.Object{ // final check - check if target root device is mounted if (btrfs_mode){ - if (repo.mount_paths["@"].length == 0){ + if (repo.mount_paths[root_subvolume_name].length == 0){ log_error(_("BTRFS device is not mounted") + ": @"); return false; } - if (include_btrfs_home_for_restore && (repo.mount_paths["@home"].length == 0)){ + if (include_btrfs_home_for_restore && (repo.mount_paths[home_subvolume_name].length == 0)){ log_error(_("BTRFS device is not mounted") + ": @home"); return false; } @@ -2428,7 +2431,7 @@ public class Main : GLib.Object{ if (!App.snapshot_to_restore.subvolumes.has_key(entry.subvolume_name())){ continue; } - if ((entry.subvolume_name() == "@home") && !include_btrfs_home_for_restore){ continue; } + if ((entry.subvolume_name() == home_subvolume_name) && !include_btrfs_home_for_restore){ continue; } } string dev_name = entry.device.full_name_with_parent; @@ -2464,7 +2467,7 @@ public class Main : GLib.Object{ if (!App.snapshot_to_restore.subvolumes.has_key(entry.subvolume_name())){ continue; } - if ((entry.subvolume_name() == "@home") && !include_btrfs_home_for_restore){ continue; } + if ((entry.subvolume_name() == home_subvolume_name) && !include_btrfs_home_for_restore){ continue; } } string dev_name = entry.device.full_name_with_parent; @@ -3156,7 +3159,7 @@ public class Main : GLib.Object{ foreach(var subvol in snapshot_to_restore.subvolumes.values){ - if ((subvol.name == "@home") && !include_btrfs_home_for_restore){ continue; } + if ((subvol.name == home_subvolume_name) && !include_btrfs_home_for_restore){ continue; } subvol.restore(); } @@ -3220,12 +3223,12 @@ public class Main : GLib.Object{ if (found){ //delete system subvolumes - if (sys_subvolumes.has_key("@") && snapshot_to_restore.subvolumes.has_key("@")){ - sys_subvolumes["@"].remove(); + if (sys_subvolumes.has_key(root_subvolume_name) && snapshot_to_restore.subvolumes.has_key(root_subvolume_name)){ + sys_subvolumes[root_subvolume_name].remove(); log_msg(_("Deleted subvolume") + ": @"); } - if (include_btrfs_home_for_restore && sys_subvolumes.has_key("@home") && snapshot_to_restore.subvolumes.has_key("@home")){ - sys_subvolumes["@home"].remove(); + if (include_btrfs_home_for_restore && sys_subvolumes.has_key(home_subvolume_name) && snapshot_to_restore.subvolumes.has_key(home_subvolume_name)){ + sys_subvolumes[home_subvolume_name].remove(); log_msg(_("Deleted subvolume") + ": @home"); } @@ -3253,9 +3256,9 @@ public class Main : GLib.Object{ var subvol_list = new Gee.ArrayList(); - var subvol_names = new string[] { "@" }; + var subvol_names = new string[] { root_subvolume_name }; if (include_btrfs_home_for_restore){ - subvol_names = new string[] { "@","@home" }; + subvol_names = new string[] { root_subvolume_name,home_subvolume_name }; } foreach(string subvol_name in subvol_names){ @@ -3284,7 +3287,7 @@ public class Main : GLib.Object{ return false; } else{ - var subvol_dev = (subvol_name == "@") ? repo.device : repo.device_home; + var subvol_dev = (subvol_name == root_subvolume_name) ? repo.device : repo.device_home; subvol_list.add(new Subvolume(subvol_name, dst_path, subvol_dev.uuid, repo)); log_msg(_("Moved system subvolume to snapshot directory") + ": %s".printf(subvol_name)); @@ -3298,11 +3301,11 @@ public class Main : GLib.Object{ else{ // write control file ----------- - snapshot_path = path_combine(repo.mount_paths["@"], "timeshift-btrfs/snapshots/%s".printf(snapshot_name)); + snapshot_path = path_combine(repo.mount_paths[root_subvolume_name], "timeshift-btrfs/snapshots/%s".printf(snapshot_name)); var snap = Snapshot.write_control_file( snapshot_path, dt_created, repo.device.uuid, - LinuxDistro.get_dist_info(path_combine(snapshot_path,"@")).full_name(), + LinuxDistro.get_dist_info(path_combine(snapshot_path,root_subvolume_name)).full_name(), "ondemand", "", 0, true, false, repo); snap.description = "Before restoring '%s'".printf(snapshot_to_restore.date_formatted); @@ -3347,6 +3350,9 @@ public class Main : GLib.Object{ config.set_string_member("parent_device_uuid", backup_parent_uuid); } + config.set_string_member("root_subvolume_name", root_subvolume_name); + config.set_string_member("home_subvolume_name", home_subvolume_name); + config.set_string_member("do_first_run", false.to_string()); config.set_string_member("btrfs_mode", btrfs_mode.to_string()); config.set_string_member("include_btrfs_home_for_backup", include_btrfs_home_for_backup.to_string()); @@ -3466,6 +3472,9 @@ public class Main : GLib.Object{ if (cmd_btrfs_mode != null){ btrfs_mode = cmd_btrfs_mode; //override } + + root_subvolume_name = json_get_string(config,"root_subvolume_name", root_subvolume_name); + home_subvolume_name = json_get_string(config,"home_subvolume_name", home_subvolume_name); backup_uuid = json_get_string(config,"backup_device_uuid", backup_uuid); backup_parent_uuid = json_get_string(config,"parent_device_uuid", backup_parent_uuid); @@ -3534,7 +3543,7 @@ public class Main : GLib.Object{ // load some defaults for first-run based on user's system type - bool supported = sys_subvolumes.has_key("@") && cmd_exists("btrfs"); // && sys_subvolumes.has_key("@home") + bool supported = sys_subvolumes.has_key(root_subvolume_name) && cmd_exists("btrfs"); // && sys_subvolumes.has_key(home_subvolume_name) if (supported || (cmd_btrfs_mode == true)){ log_msg(_("Selected default snapshot type") + ": %s".printf("BTRFS")); btrfs_mode = true; @@ -3949,8 +3958,8 @@ public class Main : GLib.Object{ update_partitions(); // In BTRFS mode, select the system disk if system disk is BTRFS - if (btrfs_mode && sys_subvolumes.has_key("@")){ - var subvol_root = sys_subvolumes["@"]; + if (btrfs_mode && sys_subvolumes.has_key(root_subvolume_name)){ + var subvol_root = sys_subvolumes[root_subvolume_name]; repo = new SnapshotRepo.from_device(subvol_root.get_device(), parent_win, btrfs_mode); return; } @@ -3973,7 +3982,7 @@ public class Main : GLib.Object{ if (dev.has_children()) { return false; } if (btrfs_mode && ((dev.fstype == "btrfs")||(dev.fstype == "luks"))){ - if (check_btrfs_volume(dev, "@", unlock)){ + if (check_btrfs_volume(dev, root_subvolume_name, unlock)){ return true; } } @@ -4158,9 +4167,9 @@ public class Main : GLib.Object{ } public bool query_subvolume_ids(){ - bool ok = query_subvolume_id("@"); + bool ok = query_subvolume_id(root_subvolume_name); if ((repo.device_home != null) && (repo.device.uuid != repo.device_home.uuid)){ - ok = ok && query_subvolume_id("@home"); + ok = ok && query_subvolume_id(home_subvolume_name); } return ok; } @@ -4198,14 +4207,14 @@ public class Main : GLib.Object{ Subvolume subvol = null; - if ((sys_subvolumes.size > 0) && line.has_suffix(sys_subvolumes["@"].path.replace(repo.mount_paths["@"] + "/"," "))){ - subvol = sys_subvolumes["@"]; + if ((sys_subvolumes.size > 0) && line.has_suffix(sys_subvolumes[root_subvolume_name].path.replace(repo.mount_paths[root_subvolume_name] + "/"," "))){ + subvol = sys_subvolumes[root_subvolume_name]; } else if ((sys_subvolumes.size > 0) - && sys_subvolumes.has_key("@home") - && line.has_suffix(sys_subvolumes["@home"].path.replace(repo.mount_paths["@home"] + "/"," "))){ + && sys_subvolumes.has_key(home_subvolume_name) + && line.has_suffix(sys_subvolumes[home_subvolume_name].path.replace(repo.mount_paths[home_subvolume_name] + "/"," "))){ - subvol = sys_subvolumes["@home"]; + subvol = sys_subvolumes[home_subvolume_name]; } else { foreach(var bak in repo.snapshots){ @@ -4227,9 +4236,9 @@ public class Main : GLib.Object{ } public bool query_subvolume_quotas(){ - bool ok = query_subvolume_quota("@"); + bool ok = query_subvolume_quota(root_subvolume_name); if (repo.device.uuid != repo.device_home.uuid){ - ok = ok && query_subvolume_quota("@home"); + ok = ok && query_subvolume_quota(home_subvolume_name); } return ok; } @@ -4290,15 +4299,15 @@ public class Main : GLib.Object{ Subvolume subvol = null; - if ((sys_subvolumes.size > 0) && (sys_subvolumes["@"].id == subvol_id)){ + if ((sys_subvolumes.size > 0) && (sys_subvolumes[root_subvolume_name].id == subvol_id)){ - subvol = sys_subvolumes["@"]; + subvol = sys_subvolumes[root_subvolume_name]; } else if ((sys_subvolumes.size > 0) - && sys_subvolumes.has_key("@home") - && (sys_subvolumes["@home"].id == subvol_id)){ + && sys_subvolumes.has_key(home_subvolume_name) + && (sys_subvolumes[home_subvolume_name].id == subvol_id)){ - subvol = sys_subvolumes["@home"]; + subvol = sys_subvolumes[home_subvolume_name]; } else { foreach(var bak in repo.snapshots){ diff --git a/src/Core/Snapshot.vala b/src/Core/Snapshot.vala index 4898925c..32880f02 100644 --- a/src/Core/Snapshot.vala +++ b/src/Core/Snapshot.vala @@ -228,7 +228,7 @@ public class Snapshot : GLib.Object{ live = json_get_bool(config,"live",false); string type = config.get_string_member_with_default("type", "rsync"); - string extension = (type == "btrfs") ? "@" : "localhost"; + string extension = (type == "btrfs") ? App.root_subvolume_name : "localhost"; distro = LinuxDistro.get_dist_info(path_combine(path, extension)); //log_debug("repo.mount_path: %s".printf(repo.mount_path)); @@ -239,7 +239,7 @@ public class Snapshot : GLib.Object{ foreach(string subvol_name in subvols.get_members()){ - if ((subvol_name != "@")&&(subvol_name != "@home")){ continue; } + if ((subvol_name != App.root_subvolume_name)&&(subvol_name != App.home_subvolume_name)){ continue; } paths[subvol_name] = path.replace(repo.mount_path, repo.mount_paths[subvol_name]); diff --git a/src/Core/SnapshotRepo.vala b/src/Core/SnapshotRepo.vala index 5be72936..650a8215 100644 --- a/src/Core/SnapshotRepo.vala +++ b/src/Core/SnapshotRepo.vala @@ -193,31 +193,31 @@ public class SnapshotRepo : GLib.Object{ } // rsync - mount_paths["@"] = ""; - mount_paths["@home"] = ""; + mount_paths[App.root_subvolume_name] = ""; + mount_paths[App.home_subvolume_name] = ""; if (btrfs_mode){ - mount_paths["@"] = mount_path; - mount_paths["@home"] = mount_path; //default + mount_paths[App.root_subvolume_name] = mount_path; + mount_paths[App.home_subvolume_name] = mount_path; //default device_home = device; //default // mount @home if on different disk ------- - var repo_subvolumes = Subvolume.detect_subvolumes_for_system_by_path(path_combine(mount_path,"@"), this, parent_window); + var repo_subvolumes = Subvolume.detect_subvolumes_for_system_by_path(path_combine(mount_path,App.root_subvolume_name), this, parent_window); - if (repo_subvolumes.has_key("@home")){ + if (repo_subvolumes.has_key(App.home_subvolume_name)){ - var subvol = repo_subvolumes["@home"]; + var subvol = repo_subvolumes[App.home_subvolume_name]; if (subvol.device_uuid != device.uuid){ // @home is on a separate device device_home = subvol.get_device(); - mount_paths["@home"] = unlock_and_mount_device(device_home, App.mount_point_app + "/backup-home"); + mount_paths[App.home_subvolume_name] = unlock_and_mount_device(device_home, App.mount_point_app + "/backup-home"); - if (mount_paths["@home"].length == 0){ + if (mount_paths[App.home_subvolume_name].length == 0){ return false; } } @@ -505,7 +505,7 @@ public class SnapshotRepo : GLib.Object{ log_debug("SnapshotRepo: has_btrfs_system()"); - var root_path = path_combine(mount_paths["@"],"@"); + var root_path = path_combine(mount_paths[App.root_subvolume_name],App.root_subvolume_name); log_debug("root_path=%s".printf(root_path)); log_debug("btrfs_mode=%s".printf(btrfs_mode.to_string())); if (btrfs_mode){ diff --git a/src/Core/Subvolume.vala b/src/Core/Subvolume.vala index 27ef3ab9..1afe5731 100644 --- a/src/Core/Subvolume.vala +++ b/src/Core/Subvolume.vala @@ -124,11 +124,11 @@ public class Subvolume : GLib.Object{ public bool remove(){ if (is_system_subvolume){ - if (name == "@"){ - path = path_combine(App.mount_point_app + "/backup", "@"); + if (name == App.root_subvolume_name){ + path = path_combine(App.mount_point_app + "/backup", App.root_subvolume_name); } - else if (name == "@home"){ - path = path_combine(App.mount_point_app + "/backup-home", "@home"); + else if (name == App.home_subvolume_name){ + path = path_combine(App.mount_point_app + "/backup-home", App.home_subvolume_name); } } diff --git a/src/Gtk/RestoreWindow.vala b/src/Gtk/RestoreWindow.vala index ab418de7..b5ca3125 100644 --- a/src/Gtk/RestoreWindow.vala +++ b/src/Gtk/RestoreWindow.vala @@ -271,7 +271,7 @@ class RestoreWindow : Gtk.Window{ if (App.btrfs_mode){ - if (App.snapshot_to_restore.subvolumes.has_key("@home")){ + if (App.snapshot_to_restore.subvolumes.has_key(App.home_subvolume_name)){ notebook.page = Tabs.USERS; } diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index 9df91918..84a3aa7d 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -37,6 +37,10 @@ class SnapshotBackendBox : Gtk.Box{ private Gtk.RadioButton opt_rsync; private Gtk.RadioButton opt_btrfs; private Gtk.Label lbl_description; + private Gtk.Label lbl_root_subvol_name; + private Gtk.Label lbl_home_subvol_name; + private Gtk.Entry entry_root_subvol; + private Gtk.Entry entry_home_subvol; private Gtk.Window parent_window; public signal void type_changed(); @@ -97,6 +101,32 @@ class SnapshotBackendBox : Gtk.Box{ hbox.add (opt); opt_btrfs = opt; + // root subvolume name layout + var vbox_root = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); + hbox.add(vbox_root); + + lbl_root_subvol_name = new Gtk.Label(_("Root subvolume name:")); + lbl_root_subvol_name.xalign = (float) 0.0; + vbox_root.add (lbl_root_subvol_name); + + entry_root_subvol = new Gtk.Entry(); + entry_root_subvol.text = App.root_subvolume_name; + //entry_root_subvol.hexpand = true; + vbox_root.add(entry_root_subvol); + + // home subvolume name layout + var vbox_home = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); + hbox.add(vbox_home); + + lbl_home_subvol_name = new Gtk.Label(_("Home subvolume name:")); + lbl_home_subvol_name.xalign = (float) 0.0; + vbox_home.add (lbl_home_subvol_name); + + entry_home_subvol = new Gtk.Entry(); + entry_home_subvol.text = App.home_subvolume_name; + //entry_home_subvol.hexpand = true; + vbox_home.add(entry_home_subvol); + if (!check_for_btrfs_tools()) { opt.sensitive = false; opt_rsync.active = true; @@ -110,6 +140,18 @@ class SnapshotBackendBox : Gtk.Box{ update_description(); } }); + + entry_root_subvol.focus_out_event.connect((entry1, event1) => { + App.root_subvolume_name = entry_root_subvol.text; + log_debug("saved root_subvolume_name: %s".printf(App.root_subvolume_name)); + return false; + }); + + entry_home_subvol.focus_out_event.connect((entry1, event1) => { + App.home_subvolume_name = entry_home_subvol.text; + log_debug("saved home_subvolume_name: %s".printf(App.home_subvolume_name)); + return false; + }); } private bool check_for_btrfs_tools() { diff --git a/src/Gtk/SnapshotListBox.vala b/src/Gtk/SnapshotListBox.vala index 8b680813..22ca598d 100644 --- a/src/Gtk/SnapshotListBox.vala +++ b/src/Gtk/SnapshotListBox.vala @@ -429,12 +429,12 @@ class SnapshotListBox : Gtk.Box{ int64 size = 0; - if (bak.subvolumes.has_key("@")){ - size += bak.subvolumes["@"].total_bytes; + if (bak.subvolumes.has_key(App.root_subvolume_name)){ + size += bak.subvolumes[App.root_subvolume_name].total_bytes; } - if (bak.subvolumes.has_key("@home")){ - size += bak.subvolumes["@home"].total_bytes; + if (bak.subvolumes.has_key(App.home_subvolume_name)){ + size += bak.subvolumes[App.home_subvolume_name].total_bytes; } ctxt.text = format_file_size(size); @@ -465,11 +465,11 @@ class SnapshotListBox : Gtk.Box{ int64 size = 0; - if (bak.subvolumes.has_key("@")){ - size += bak.subvolumes["@"].unshared_bytes; + if (bak.subvolumes.has_key(App.root_subvolume_name)){ + size += bak.subvolumes[App.root_subvolume_name].unshared_bytes; } - if (bak.subvolumes.has_key("@home")){ - size += bak.subvolumes["@home"].unshared_bytes; + if (bak.subvolumes.has_key(App.home_subvolume_name)){ + size += bak.subvolumes[App.home_subvolume_name].unshared_bytes; } ctxt.text = format_file_size(size); From 6cf874577fc21b9e2ef6212c887ff7e56682445e Mon Sep 17 00:00:00 2001 From: fused01 Date: Sun, 23 Nov 2025 16:56:07 +0100 Subject: [PATCH 02/29] Improve subvolume name option layout --- src/Gtk/SnapshotBackendBox.vala | 67 ++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index 84a3aa7d..2338f895 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -37,8 +37,8 @@ class SnapshotBackendBox : Gtk.Box{ private Gtk.RadioButton opt_rsync; private Gtk.RadioButton opt_btrfs; private Gtk.Label lbl_description; - private Gtk.Label lbl_root_subvol_name; - private Gtk.Label lbl_home_subvol_name; + private Gtk.Box hbox_subvolume_root; + private Gtk.Box hbox_subvolume_home; private Gtk.Entry entry_root_subvol; private Gtk.Entry entry_home_subvol; private Gtk.Window parent_window; @@ -86,6 +86,8 @@ class SnapshotBackendBox : Gtk.Box{ opt_rsync.toggled.connect(()=>{ if (opt_rsync.active){ App.btrfs_mode = false; + hbox_subvolume_root.visible = false; + hbox_subvolume_home.visible = false; Main.first_snapshot_size = 0; init_backend(); type_changed(); @@ -101,31 +103,7 @@ class SnapshotBackendBox : Gtk.Box{ hbox.add (opt); opt_btrfs = opt; - // root subvolume name layout - var vbox_root = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); - hbox.add(vbox_root); - - lbl_root_subvol_name = new Gtk.Label(_("Root subvolume name:")); - lbl_root_subvol_name.xalign = (float) 0.0; - vbox_root.add (lbl_root_subvol_name); - - entry_root_subvol = new Gtk.Entry(); - entry_root_subvol.text = App.root_subvolume_name; - //entry_root_subvol.hexpand = true; - vbox_root.add(entry_root_subvol); - - // home subvolume name layout - var vbox_home = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); - hbox.add(vbox_home); - - lbl_home_subvol_name = new Gtk.Label(_("Home subvolume name:")); - lbl_home_subvol_name.xalign = (float) 0.0; - vbox_home.add (lbl_home_subvol_name); - - entry_home_subvol = new Gtk.Entry(); - entry_home_subvol.text = App.home_subvolume_name; - //entry_home_subvol.hexpand = true; - vbox_home.add(entry_home_subvol); + add_opt_btrfs_subvolume_names(hbox); if (!check_for_btrfs_tools()) { opt.sensitive = false; @@ -135,11 +113,46 @@ class SnapshotBackendBox : Gtk.Box{ opt_btrfs.toggled.connect(()=>{ if (opt_btrfs.active){ App.btrfs_mode = true; + hbox_subvolume_root.visible = true; + hbox_subvolume_home.visible = true; init_backend(); type_changed(); update_description(); } }); + } + + private void add_opt_btrfs_subvolume_names(Gtk.Box hbox){ + var sg_title = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); + var sg_edit = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); + + // root subvolume name layout + hbox_subvolume_root = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); + hbox.add(hbox_subvolume_root); + + var lbl_root_subvol_name = new Gtk.Label(_("Root subvolume:")); + lbl_root_subvol_name.xalign = (float) 0.0; + hbox_subvolume_root.add (lbl_root_subvol_name); + sg_title.add_widget(lbl_root_subvol_name); + + entry_root_subvol = new Gtk.Entry(); + entry_root_subvol.text = App.root_subvolume_name; + hbox_subvolume_root.add(entry_root_subvol); + sg_edit.add_widget(entry_root_subvol); + + // home subvolume name layout + hbox_subvolume_home = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); + hbox.add(hbox_subvolume_home); + + var lbl_home_subvol_name = new Gtk.Label(_("Home subvolume:")); + lbl_home_subvol_name.xalign = (float) 0.0; + hbox_subvolume_home.add (lbl_home_subvol_name); + sg_title.add_widget(lbl_home_subvol_name); + + entry_home_subvol = new Gtk.Entry(); + entry_home_subvol.text = App.home_subvolume_name; + hbox_subvolume_home.add(entry_home_subvol); + sg_edit.add_widget(entry_home_subvol); entry_root_subvol.focus_out_event.connect((entry1, event1) => { App.root_subvolume_name = entry_root_subvol.text; From bd54ed4b22be63f98599d0618edea887e002b714 Mon Sep 17 00:00:00 2001 From: fused01 Date: Sun, 23 Nov 2025 17:39:32 +0100 Subject: [PATCH 03/29] early out of subvolume listing if configured subvolume is not the same as system subvolume name --- src/Core/Main.vala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 5b6707ac..1d247c43 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -4173,6 +4173,10 @@ public class Main : GLib.Object{ public bool query_subvolume_id(string subvol_name){ log_debug("query_subvolume_id():%s".printf(subvol_name)); + + // Early out when configured subvolume name != actual. + if(sys_subvolumes[root_subvolume_name] == null || sys_subvolumes[home_subvolume_name] == null) + return; string cmd = ""; string std_out; From f3e3bc840a8dd45a1aaa719ed2ddf86e15517d57 Mon Sep 17 00:00:00 2001 From: fused0 Date: Sun, 23 Nov 2025 18:48:46 +0100 Subject: [PATCH 04/29] fix add missing return value in Main.query_subvolume_id --- src/Core/Main.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 1d247c43..c95617b3 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -4176,7 +4176,7 @@ public class Main : GLib.Object{ // Early out when configured subvolume name != actual. if(sys_subvolumes[root_subvolume_name] == null || sys_subvolumes[home_subvolume_name] == null) - return; + return false; string cmd = ""; string std_out; From 52b7cf4055f0da3e2dfce3c53cb1cab9c53beeb3 Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 24 Nov 2025 08:41:48 +0100 Subject: [PATCH 05/29] Subvolume selection is now a combobox Also deduplicated the subvolume ui selection code --- src/Gtk/SnapshotBackendBox.vala | 111 ++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index 2338f895..e77a4895 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -37,10 +37,8 @@ class SnapshotBackendBox : Gtk.Box{ private Gtk.RadioButton opt_rsync; private Gtk.RadioButton opt_btrfs; private Gtk.Label lbl_description; - private Gtk.Box hbox_subvolume_root; - private Gtk.Box hbox_subvolume_home; - private Gtk.Entry entry_root_subvol; - private Gtk.Entry entry_home_subvol; + private Gtk.ComboBox combo_root_subvol; + private Gtk.ComboBox combo_home_subvol; private Gtk.Window parent_window; public signal void type_changed(); @@ -86,8 +84,8 @@ class SnapshotBackendBox : Gtk.Box{ opt_rsync.toggled.connect(()=>{ if (opt_rsync.active){ App.btrfs_mode = false; - hbox_subvolume_root.visible = false; - hbox_subvolume_home.visible = false; + combo_root_subvol.sensitive = false; + combo_home_subvol.sensitive = false; Main.first_snapshot_size = 0; init_backend(); type_changed(); @@ -113,8 +111,8 @@ class SnapshotBackendBox : Gtk.Box{ opt_btrfs.toggled.connect(()=>{ if (opt_btrfs.active){ App.btrfs_mode = true; - hbox_subvolume_root.visible = true; - hbox_subvolume_home.visible = true; + combo_root_subvol.sensitive = true; + combo_home_subvol.sensitive = true; init_backend(); type_changed(); update_description(); @@ -122,49 +120,68 @@ class SnapshotBackendBox : Gtk.Box{ }); } + private Gtk.ComboBox create_btrfs_subvolume_selection(string label, string value, + string[] possibleValues, Gtk.Box hbox, Gtk.SizeGroup sg_title, Gtk.SizeGroup sg_edit) { + // root subvolume name layout + var hbox_subvolume = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); + hbox.add(hbox_subvolume); + + var lbl_subvol_name = new Gtk.Label(_(@"$label subvolume:")); + lbl_subvol_name.xalign = (float) 0.0; + hbox_subvolume.add(lbl_subvol_name); + sg_title.add_widget(lbl_subvol_name); + + Gtk.ListStore list_store = new Gtk.ListStore (1, typeof (string)); + Gtk.TreeIter strore_iter; + int index = 0; + int active = -1; + foreach(var curr in possibleValues){ + list_store.append(out strore_iter); + list_store.set(strore_iter, 0, curr); + + if (curr == value) active = index; + index++; + } + + Gtk.ComboBox combo_subvol = new Gtk.ComboBox.with_model (list_store); + hbox_subvolume.add (combo_subvol); + sg_edit.add_widget(combo_subvol); + + Gtk.CellRendererText renderer = new Gtk.CellRendererText (); + combo_subvol.pack_start (renderer, true); + combo_subvol.add_attribute (renderer, "text", 0); + combo_subvol.active = active; + + combo_subvol.changed.connect (() => { + Value val; + Gtk.TreeIter iter; + combo_subvol.get_active_iter (out iter); + list_store.get_value (iter, 0, out val); + // Note: this could probably be improved + if (label == "Root") App.root_subvolume_name = (string) val; + else App.home_subvolume_name = (string) val; + }); + + return combo_subvol; + } + private void add_opt_btrfs_subvolume_names(Gtk.Box hbox){ var sg_title = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); var sg_edit = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); - // root subvolume name layout - hbox_subvolume_root = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); - hbox.add(hbox_subvolume_root); - - var lbl_root_subvol_name = new Gtk.Label(_("Root subvolume:")); - lbl_root_subvol_name.xalign = (float) 0.0; - hbox_subvolume_root.add (lbl_root_subvol_name); - sg_title.add_widget(lbl_root_subvol_name); - - entry_root_subvol = new Gtk.Entry(); - entry_root_subvol.text = App.root_subvolume_name; - hbox_subvolume_root.add(entry_root_subvol); - sg_edit.add_widget(entry_root_subvol); - - // home subvolume name layout - hbox_subvolume_home = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); - hbox.add(hbox_subvolume_home); - - var lbl_home_subvol_name = new Gtk.Label(_("Home subvolume:")); - lbl_home_subvol_name.xalign = (float) 0.0; - hbox_subvolume_home.add (lbl_home_subvol_name); - sg_title.add_widget(lbl_home_subvol_name); - - entry_home_subvol = new Gtk.Entry(); - entry_home_subvol.text = App.home_subvolume_name; - hbox_subvolume_home.add(entry_home_subvol); - sg_edit.add_widget(entry_home_subvol); - - entry_root_subvol.focus_out_event.connect((entry1, event1) => { - App.root_subvolume_name = entry_root_subvol.text; - log_debug("saved root_subvolume_name: %s".printf(App.root_subvolume_name)); - return false; - }); - - entry_home_subvol.focus_out_event.connect((entry1, event1) => { - App.home_subvolume_name = entry_home_subvol.text; - log_debug("saved home_subvolume_name: %s".printf(App.home_subvolume_name)); - return false; - }); + combo_root_subvol = create_btrfs_subvolume_selection("Root", + App.root_subvolume_name, new string[]{ + "@", + "@rootfs", + "root" + }, hbox, sg_title, sg_edit); + + combo_home_subvol = create_btrfs_subvolume_selection("Home", + App.home_subvolume_name, new string[]{ + "@home", + "@homefs", + "home" + }, hbox, sg_title, sg_edit); } private bool check_for_btrfs_tools() { From 9cc883bf031f5641d186489d70f6351384bba09e Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 24 Nov 2025 10:23:31 +0100 Subject: [PATCH 06/29] improved subvolume selection by adding a distribution hint --- src/Gtk/SnapshotBackendBox.vala | 47 ++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index e77a4895..5ccb557d 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -121,7 +121,7 @@ class SnapshotBackendBox : Gtk.Box{ } private Gtk.ComboBox create_btrfs_subvolume_selection(string label, string value, - string[] possibleValues, Gtk.Box hbox, Gtk.SizeGroup sg_title, Gtk.SizeGroup sg_edit) { + string[,] possibleValues, Gtk.Box hbox, Gtk.SizeGroup sg_label, Gtk.SizeGroup sg_combo) { // root subvolume name layout var hbox_subvolume = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); hbox.add(hbox_subvolume); @@ -129,27 +129,32 @@ class SnapshotBackendBox : Gtk.Box{ var lbl_subvol_name = new Gtk.Label(_(@"$label subvolume:")); lbl_subvol_name.xalign = (float) 0.0; hbox_subvolume.add(lbl_subvol_name); - sg_title.add_widget(lbl_subvol_name); + sg_label.add_widget(lbl_subvol_name); - Gtk.ListStore list_store = new Gtk.ListStore (1, typeof (string)); + Gtk.ListStore list_store = new Gtk.ListStore (2, typeof (string), typeof (string)); Gtk.TreeIter strore_iter; - int index = 0; int active = -1; - foreach(var curr in possibleValues){ + for (int idx = 0; idx < possibleValues.length[0]; idx++) { list_store.append(out strore_iter); - list_store.set(strore_iter, 0, curr); + list_store.set(strore_iter, 0, possibleValues[idx, 0], 1, possibleValues[idx, 1]); - if (curr == value) active = index; - index++; + // Find out value in the options + if (possibleValues[idx, 0] == value) active = idx; } Gtk.ComboBox combo_subvol = new Gtk.ComboBox.with_model (list_store); hbox_subvolume.add (combo_subvol); - sg_edit.add_widget(combo_subvol); + sg_combo.add_widget(combo_subvol); Gtk.CellRendererText renderer = new Gtk.CellRendererText (); combo_subvol.pack_start (renderer, true); combo_subvol.add_attribute (renderer, "text", 0); + + renderer = new Gtk.CellRendererText (); + combo_subvol.pack_start (renderer, true); + combo_subvol.add_attribute (renderer, "text", 1); + + // Set active index combo_subvol.active = active; combo_subvol.changed.connect (() => { @@ -166,22 +171,22 @@ class SnapshotBackendBox : Gtk.Box{ } private void add_opt_btrfs_subvolume_names(Gtk.Box hbox){ - var sg_title = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); - var sg_edit = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); + var sg_label = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); + var sg_combo = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); combo_root_subvol = create_btrfs_subvolume_selection("Root", - App.root_subvolume_name, new string[]{ - "@", - "@rootfs", - "root" - }, hbox, sg_title, sg_edit); + App.root_subvolume_name, new string[,]{ + {"@", "Ubuntu"}, + {"@rootfs", "Debian"}, + {"root", "Fedora"} + }, hbox, sg_label, sg_combo); combo_home_subvol = create_btrfs_subvolume_selection("Home", - App.home_subvolume_name, new string[]{ - "@home", - "@homefs", - "home" - }, hbox, sg_title, sg_edit); + App.home_subvolume_name, new string[,]{ + {"@home", "Ubuntu"}, + {"@homefs", "Debian"}, + {"home", "Fedora"} + }, hbox, sg_label, sg_combo); } private bool check_for_btrfs_tools() { From 0ebe92a30addb00f8f1c85b7295ddcd83cf70a5d Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 24 Nov 2025 10:43:34 +0100 Subject: [PATCH 07/29] combine subvol comboboxes into a single combobox to select a predefined layout --- src/Gtk/SnapshotBackendBox.vala | 71 +++++++++++++++++---------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index 5ccb557d..4e714476 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -37,8 +37,7 @@ class SnapshotBackendBox : Gtk.Box{ private Gtk.RadioButton opt_rsync; private Gtk.RadioButton opt_btrfs; private Gtk.Label lbl_description; - private Gtk.ComboBox combo_root_subvol; - private Gtk.ComboBox combo_home_subvol; + private Gtk.ComboBox combo_subvol_layout; private Gtk.Window parent_window; public signal void type_changed(); @@ -84,8 +83,7 @@ class SnapshotBackendBox : Gtk.Box{ opt_rsync.toggled.connect(()=>{ if (opt_rsync.active){ App.btrfs_mode = false; - combo_root_subvol.sensitive = false; - combo_home_subvol.sensitive = false; + combo_subvol_layout.sensitive = false; Main.first_snapshot_size = 0; init_backend(); type_changed(); @@ -111,8 +109,7 @@ class SnapshotBackendBox : Gtk.Box{ opt_btrfs.toggled.connect(()=>{ if (opt_btrfs.active){ App.btrfs_mode = true; - combo_root_subvol.sensitive = true; - combo_home_subvol.sensitive = true; + combo_subvol_layout.sensitive = true; init_backend(); type_changed(); update_description(); @@ -120,26 +117,34 @@ class SnapshotBackendBox : Gtk.Box{ }); } - private Gtk.ComboBox create_btrfs_subvolume_selection(string label, string value, - string[,] possibleValues, Gtk.Box hbox, Gtk.SizeGroup sg_label, Gtk.SizeGroup sg_combo) { + private Gtk.ComboBox create_btrfs_subvolume_selection(string[] value, + string[,] possibleValues, Gtk.Box hbox, Gtk.SizeGroup sg_label, + Gtk.SizeGroup sg_combo) { // root subvolume name layout var hbox_subvolume = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); hbox.add(hbox_subvolume); - var lbl_subvol_name = new Gtk.Label(_(@"$label subvolume:")); + var lbl_subvol_name = new Gtk.Label(_("Subvolume layout:")); lbl_subvol_name.xalign = (float) 0.0; hbox_subvolume.add(lbl_subvol_name); sg_label.add_widget(lbl_subvol_name); - Gtk.ListStore list_store = new Gtk.ListStore (2, typeof (string), typeof (string)); + Gtk.ListStore list_store = new Gtk.ListStore (3, + typeof (string), + typeof (string), + typeof (string)); Gtk.TreeIter strore_iter; int active = -1; for (int idx = 0; idx < possibleValues.length[0]; idx++) { list_store.append(out strore_iter); - list_store.set(strore_iter, 0, possibleValues[idx, 0], 1, possibleValues[idx, 1]); + list_store.set(strore_iter, + 0, possibleValues[idx, 0], + 1, possibleValues[idx, 1], + 2, possibleValues[idx, 2]); // Find out value in the options - if (possibleValues[idx, 0] == value) active = idx; + if (possibleValues[idx, 0] == value[0] && + possibleValues[idx, 1] == value[1]) active = idx; } Gtk.ComboBox combo_subvol = new Gtk.ComboBox.with_model (list_store); @@ -148,23 +153,24 @@ class SnapshotBackendBox : Gtk.Box{ Gtk.CellRendererText renderer = new Gtk.CellRendererText (); combo_subvol.pack_start (renderer, true); - combo_subvol.add_attribute (renderer, "text", 0); - - renderer = new Gtk.CellRendererText (); - combo_subvol.pack_start (renderer, true); - combo_subvol.add_attribute (renderer, "text", 1); + combo_subvol.add_attribute (renderer, "text", 2); // Set active index combo_subvol.active = active; combo_subvol.changed.connect (() => { - Value val; Gtk.TreeIter iter; combo_subvol.get_active_iter (out iter); - list_store.get_value (iter, 0, out val); - // Note: this could probably be improved - if (label == "Root") App.root_subvolume_name = (string) val; - else App.home_subvolume_name = (string) val; + + Value val1; + list_store.get_value (iter, 0, out val1); + App.root_subvolume_name = (string) val1; + + Value val2; + list_store.get_value (iter, 1, out val2); + App.home_subvolume_name = (string) val2; + + //print("Selected layout: %s %s\n", (string) val1, (string) val2); }); return combo_subvol; @@ -174,18 +180,15 @@ class SnapshotBackendBox : Gtk.Box{ var sg_label = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); var sg_combo = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); - combo_root_subvol = create_btrfs_subvolume_selection("Root", - App.root_subvolume_name, new string[,]{ - {"@", "Ubuntu"}, - {"@rootfs", "Debian"}, - {"root", "Fedora"} - }, hbox, sg_label, sg_combo); - - combo_home_subvol = create_btrfs_subvolume_selection("Home", - App.home_subvolume_name, new string[,]{ - {"@home", "Ubuntu"}, - {"@homefs", "Debian"}, - {"home", "Fedora"} + combo_subvol_layout = create_btrfs_subvolume_selection( + new string[]{ + App.root_subvolume_name, + App.home_subvolume_name + }, new string[,]{ + //{"", "", "Custom"}, // TODO? + {"@", "@home", "Ubuntu (@, @home)"}, + {"@rootfs", "@homefs", "Debian (@rootfs, @homefs)"}, + {"root", "home", "Fedora (root, home)"} }, hbox, sg_label, sg_combo); } From 4e131563ac203df6f2e39c60e5cf3e4ee3402d99 Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 24 Nov 2025 13:18:23 +0100 Subject: [PATCH 08/29] detect subvolume layout on first launch of timeshift, based on distro id --- src/Core/Main.vala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index c95617b3..c8a0618f 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -295,6 +295,22 @@ public class Main : GLib.Object{ this.app_conf_path_old = "/etc/timeshift.json"; this.app_conf_path_default = GLib.Path.build_path (GLib.Path.DIR_SEPARATOR_S, Constants.SYSCONFDIR, "timeshift", "default.json"); //sys_root and sys_home will be initialized by update_partition_list() + + // Detect subvolume names based on distro id. + // Only has effect when timeshift is opened the first time, + // otherwise the setting is overwritten by loading the config. + if (this.current_distro.dist_id.down() == "fedora") { + this.root_subvolume_name = "root"; + this.home_subvolume_name = "home"; + } + else if (this.current_distro.dist_id.down() == "debian") { + this.root_subvolume_name = "@rootfs"; + this.home_subvolume_name = "@homefs"; + } + else { //if (this.current_distro.dist_id.down() == "ubuntu") + this.root_subvolume_name = "@"; + this.home_subvolume_name = "@home"; + } // check if running locally ------------------------ From 04bfffd883fd5982052654996a306aa730f31af2 Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 24 Nov 2025 15:29:33 +0100 Subject: [PATCH 09/29] cleanup create_btrfs_subvolume_selection --- src/Gtk/SnapshotBackendBox.vala | 73 ++++++++++++++++----------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index 4e714476..b74f4203 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -99,7 +99,7 @@ class SnapshotBackendBox : Gtk.Box{ hbox.add (opt); opt_btrfs = opt; - add_opt_btrfs_subvolume_names(hbox); + create_btrfs_subvolume_selection(hbox); if (!check_for_btrfs_tools()) { opt.sensitive = false; @@ -117,50 +117,65 @@ class SnapshotBackendBox : Gtk.Box{ }); } - private Gtk.ComboBox create_btrfs_subvolume_selection(string[] value, - string[,] possibleValues, Gtk.Box hbox, Gtk.SizeGroup sg_label, - Gtk.SizeGroup sg_combo) { - // root subvolume name layout + private void create_btrfs_subvolume_selection(Gtk.Box hbox) { + + // subvolume layout var hbox_subvolume = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); hbox.add(hbox_subvolume); + var sg_label = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); + var sg_combo = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); + var lbl_subvol_name = new Gtk.Label(_("Subvolume layout:")); lbl_subvol_name.xalign = (float) 0.0; hbox_subvolume.add(lbl_subvol_name); sg_label.add_widget(lbl_subvol_name); + // Combobox + var layout = new string[]{ + App.root_subvolume_name, + App.home_subvolume_name + }; + + var possible_layouts = new string[,]{ + //{"", "", "Custom"}, // TODO? + {"@", "@home", "Ubuntu (@, @home)"}, + {"@rootfs", "@homefs", "Debian (@rootfs, @homefs)"}, + {"root", "home", "Fedora (root, home)"} + }; + Gtk.ListStore list_store = new Gtk.ListStore (3, typeof (string), typeof (string), typeof (string)); Gtk.TreeIter strore_iter; int active = -1; - for (int idx = 0; idx < possibleValues.length[0]; idx++) { + for (int idx = 0; idx < possible_layouts.length[0]; idx++) { list_store.append(out strore_iter); list_store.set(strore_iter, - 0, possibleValues[idx, 0], - 1, possibleValues[idx, 1], - 2, possibleValues[idx, 2]); + 0, possible_layouts[idx, 0], + 1, possible_layouts[idx, 1], + 2, possible_layouts[idx, 2]); - // Find out value in the options - if (possibleValues[idx, 0] == value[0] && - possibleValues[idx, 1] == value[1]) active = idx; + // Find our layout in the options + if (possible_layouts[idx, 0] == layout[0] && + possible_layouts[idx, 1] == layout[1]) active = idx; } - Gtk.ComboBox combo_subvol = new Gtk.ComboBox.with_model (list_store); - hbox_subvolume.add (combo_subvol); - sg_combo.add_widget(combo_subvol); + combo_subvol_layout = new Gtk.ComboBox.with_model (list_store); + hbox_subvolume.add (combo_subvol_layout); + sg_combo.add_widget(combo_subvol_layout); Gtk.CellRendererText renderer = new Gtk.CellRendererText (); - combo_subvol.pack_start (renderer, true); - combo_subvol.add_attribute (renderer, "text", 2); + combo_subvol_layout.pack_start (renderer, true); + combo_subvol_layout.add_attribute (renderer, "text", 2); // Set active index - combo_subvol.active = active; + combo_subvol_layout.active = active; - combo_subvol.changed.connect (() => { + combo_subvol_layout.changed.connect (() => { Gtk.TreeIter iter; - combo_subvol.get_active_iter (out iter); + combo_subvol_layout.get_active_iter (out iter); Value val1; list_store.get_value (iter, 0, out val1); @@ -172,24 +187,6 @@ class SnapshotBackendBox : Gtk.Box{ //print("Selected layout: %s %s\n", (string) val1, (string) val2); }); - - return combo_subvol; - } - - private void add_opt_btrfs_subvolume_names(Gtk.Box hbox){ - var sg_label = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); - var sg_combo = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); - - combo_subvol_layout = create_btrfs_subvolume_selection( - new string[]{ - App.root_subvolume_name, - App.home_subvolume_name - }, new string[,]{ - //{"", "", "Custom"}, // TODO? - {"@", "@home", "Ubuntu (@, @home)"}, - {"@rootfs", "@homefs", "Debian (@rootfs, @homefs)"}, - {"root", "home", "Fedora (root, home)"} - }, hbox, sg_label, sg_combo); } private bool check_for_btrfs_tools() { From dd769f5467c3e283d39e6cd7dcaaa307e38c20c7 Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 24 Nov 2025 16:24:31 +0100 Subject: [PATCH 10/29] call init_backend on layout change --- src/Gtk/SnapshotBackendBox.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index b74f4203..72ac44e8 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -185,7 +185,7 @@ class SnapshotBackendBox : Gtk.Box{ list_store.get_value (iter, 1, out val2); App.home_subvolume_name = (string) val2; - //print("Selected layout: %s %s\n", (string) val1, (string) val2); + init_backend(); }); } From d7b5070c839256df40ce530983b15b6523078fa3 Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 24 Nov 2025 16:45:45 +0100 Subject: [PATCH 11/29] also call type_changed on subvol layout change --- src/Gtk/SnapshotBackendBox.vala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index 72ac44e8..0af2e021 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -186,6 +186,7 @@ class SnapshotBackendBox : Gtk.Box{ App.home_subvolume_name = (string) val2; init_backend(); + type_changed(); }); } From 0e7104609651a16da60da846287629e4e7a28208 Mon Sep 17 00:00:00 2001 From: fused0 Date: Sun, 30 Nov 2025 15:41:36 +0100 Subject: [PATCH 12/29] Disable "include home subvol" option when home subvolume name is empty --- src/Gtk/UsersBox.vala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Gtk/UsersBox.vala b/src/Gtk/UsersBox.vala index be549c07..f9370c08 100644 --- a/src/Gtk/UsersBox.vala +++ b/src/Gtk/UsersBox.vala @@ -357,6 +357,9 @@ class UsersBox : Gtk.Box{ box_btrfs.set_no_show_all(false); box_btrfs.show_all(); + + if (App.home_subvolume_name == "") chk_include_btrfs_home.sensitive = false; + else chk_include_btrfs_home.sensitive = true; if (restore_mode){ chk_include_btrfs_home.active = App.include_btrfs_home_for_restore; From 7f55d830fb7b2c100653d2a522c0c8197ed8f8cb Mon Sep 17 00:00:00 2001 From: fused0 Date: Sun, 30 Nov 2025 15:43:48 +0100 Subject: [PATCH 13/29] fix for debian style layout empty home subvolume name and force include_btrfs_home_for_backup to false --- src/Core/Main.vala | 3 ++- src/Gtk/SnapshotBackendBox.vala | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index ea2d664d..47ae9de5 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -302,7 +302,8 @@ public class Main : GLib.Object{ } else if (this.current_distro.dist_id.down() == "debian") { this.root_subvolume_name = "@rootfs"; - this.home_subvolume_name = "@homefs"; + this.home_subvolume_name = ""; + this.include_btrfs_home_for_backup = false; } else { //if (this.current_distro.dist_id.down() == "ubuntu") this.root_subvolume_name = "@"; diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index 0af2e021..38229bd1 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -140,7 +140,7 @@ class SnapshotBackendBox : Gtk.Box{ var possible_layouts = new string[,]{ //{"", "", "Custom"}, // TODO? {"@", "@home", "Ubuntu (@, @home)"}, - {"@rootfs", "@homefs", "Debian (@rootfs, @homefs)"}, + {"@rootfs", "", "Debian (@rootfs)"}, {"root", "home", "Fedora (root, home)"} }; @@ -185,6 +185,10 @@ class SnapshotBackendBox : Gtk.Box{ list_store.get_value (iter, 1, out val2); App.home_subvolume_name = (string) val2; + // If home subolume name is empty, do not backup home. + if (App.home_subvolume_name == "") + App.include_btrfs_home_for_backup = false; + init_backend(); type_changed(); }); From aaccff3ccdbe6bf4e37ac0de738af11b31bcb55c Mon Sep 17 00:00:00 2001 From: fused0 Date: Sun, 30 Nov 2025 20:14:21 +0100 Subject: [PATCH 14/29] added ui for custom subvolume layout --- src/Gtk/SnapshotBackendBox.vala | 96 +++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 11 deletions(-) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index 38229bd1..a11c35cd 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -39,6 +39,7 @@ class SnapshotBackendBox : Gtk.Box{ private Gtk.Label lbl_description; private Gtk.ComboBox combo_subvol_layout; private Gtk.Window parent_window; + private Gtk.Box vbox_subvolume_custom; public signal void type_changed(); @@ -117,14 +118,14 @@ class SnapshotBackendBox : Gtk.Box{ }); } - private void create_btrfs_subvolume_selection(Gtk.Box hbox) { + private void create_btrfs_subvolume_selection(Gtk.Box vbox) { // subvolume layout var hbox_subvolume = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); - hbox.add(hbox_subvolume); + vbox.add(hbox_subvolume); var sg_label = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); - var sg_combo = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); + var sg_edit = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL); var lbl_subvol_name = new Gtk.Label(_("Subvolume layout:")); lbl_subvol_name.xalign = (float) 0.0; @@ -138,7 +139,7 @@ class SnapshotBackendBox : Gtk.Box{ }; var possible_layouts = new string[,]{ - //{"", "", "Custom"}, // TODO? + {"", "", "Custom"}, {"@", "@home", "Ubuntu (@, @home)"}, {"@rootfs", "", "Debian (@rootfs)"}, {"root", "home", "Fedora (root, home)"} @@ -162,9 +163,13 @@ class SnapshotBackendBox : Gtk.Box{ possible_layouts[idx, 1] == layout[1]) active = idx; } + if (active < 0){ + active = 0; + } + combo_subvol_layout = new Gtk.ComboBox.with_model (list_store); hbox_subvolume.add (combo_subvol_layout); - sg_combo.add_widget(combo_subvol_layout); + sg_edit.add_widget(combo_subvol_layout); Gtk.CellRendererText renderer = new Gtk.CellRendererText (); combo_subvol_layout.pack_start (renderer, true); @@ -173,17 +178,56 @@ class SnapshotBackendBox : Gtk.Box{ // Set active index combo_subvol_layout.active = active; + // Create custom inputs + vbox_subvolume_custom = new Gtk.Box(Gtk.Orientation.VERTICAL, 6); + vbox.add(vbox_subvolume_custom); + + var custom_root_subvol_entry = add_opt_btrfs_subvolume_name_entry(vbox_subvolume_custom, sg_label, sg_edit, + "Root", App.root_subvolume_name); + + var custom_home_subvol_entry = add_opt_btrfs_subvolume_name_entry(vbox_subvolume_custom, sg_label, sg_edit, + "Home", App.home_subvolume_name); + combo_subvol_layout.changed.connect (() => { Gtk.TreeIter iter; combo_subvol_layout.get_active_iter (out iter); - Value val1; - list_store.get_value (iter, 0, out val1); - App.root_subvolume_name = (string) val1; + // Handle custom names + if (combo_subvol_layout.active == 0) { + custom_root_subvol_entry.text = App.root_subvolume_name; + custom_home_subvol_entry.text = App.home_subvolume_name; + } + // Handle selection from combobox + else { + Value val1; + list_store.get_value (iter, 0, out val1); + App.root_subvolume_name = (string) val1; + + Value val2; + list_store.get_value (iter, 1, out val2); + App.home_subvolume_name = (string) val2; + + // If home subolume name is empty, do not backup home. + if (App.home_subvolume_name == "") + App.include_btrfs_home_for_backup = false; + } + + init_backend(); + type_changed(); + update_custom_subvol_name_visibility(); + }); - Value val2; - list_store.get_value (iter, 1, out val2); - App.home_subvolume_name = (string) val2; + custom_root_subvol_entry.focus_out_event.connect((entry1, event1) => { + App.root_subvolume_name = custom_root_subvol_entry.text; + + init_backend(); + type_changed(); + + return false; + }); + + custom_home_subvol_entry.focus_out_event.connect((entry1, event1) => { + App.home_subvolume_name = custom_home_subvol_entry.text; // If home subolume name is empty, do not backup home. if (App.home_subvolume_name == "") @@ -191,7 +235,36 @@ class SnapshotBackendBox : Gtk.Box{ init_backend(); type_changed(); + + return false; }); + + // Add custom subvolume names + } + + private Gtk.Entry add_opt_btrfs_subvolume_name_entry(Gtk.Box vbox, Gtk.SizeGroup sg_title, + Gtk.SizeGroup sg_edit, string name, string value) { + // root subvolume name layout + var hbox_subvolume_edit = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); + vbox.add(hbox_subvolume_edit); + + var lbl_subvol_name = new Gtk.Label(_(@"$name subvolume:")); + lbl_subvol_name.xalign = (float) 0.0; + hbox_subvolume_edit.add(lbl_subvol_name); + sg_title.add_widget(lbl_subvol_name); + + var entry_subvol = new Gtk.Entry(); + entry_subvol.text = value; + hbox_subvolume_edit.add(entry_subvol); + sg_edit.add_widget(entry_subvol); + + return entry_subvol; + } + + public void update_custom_subvol_name_visibility() { + if(combo_subvol_layout.active == 0) + vbox_subvolume_custom.visible = true; + else vbox_subvolume_custom.visible = false; } private bool check_for_btrfs_tools() { @@ -296,5 +369,6 @@ class SnapshotBackendBox : Gtk.Box{ opt_btrfs.active = App.btrfs_mode; type_changed(); update_description(); + update_custom_subvol_name_visibility(); } } From 44edb098959e65b6c7052ffdda9271fdaafd36ac Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 8 Dec 2025 17:31:10 +0100 Subject: [PATCH 15/29] Change variable name in SnapshotBackendBox Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Gtk/SnapshotBackendBox.vala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index a11c35cd..6b0b0762 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -149,11 +149,11 @@ class SnapshotBackendBox : Gtk.Box{ typeof (string), typeof (string), typeof (string)); - Gtk.TreeIter strore_iter; + Gtk.TreeIter store_iter; int active = -1; for (int idx = 0; idx < possible_layouts.length[0]; idx++) { - list_store.append(out strore_iter); - list_store.set(strore_iter, + list_store.append(out store_iter); + list_store.set(store_iter, 0, possible_layouts[idx, 0], 1, possible_layouts[idx, 1], 2, possible_layouts[idx, 2]); From 7528ba9e214cfbbf073874769b3ef2aad12d1537 Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 8 Dec 2025 17:38:43 +0100 Subject: [PATCH 16/29] Simplify toggling checkbox sensitivity in UsersBox Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Gtk/UsersBox.vala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Gtk/UsersBox.vala b/src/Gtk/UsersBox.vala index f9370c08..00c1cd05 100644 --- a/src/Gtk/UsersBox.vala +++ b/src/Gtk/UsersBox.vala @@ -358,8 +358,7 @@ class UsersBox : Gtk.Box{ box_btrfs.set_no_show_all(false); box_btrfs.show_all(); - if (App.home_subvolume_name == "") chk_include_btrfs_home.sensitive = false; - else chk_include_btrfs_home.sensitive = true; + chk_include_btrfs_home.sensitive = (App.home_subvolume_name != ""); if (restore_mode){ chk_include_btrfs_home.active = App.include_btrfs_home_for_restore; From deb0ecbdd2eec4a16a29285c69c98efe4a106f56 Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 8 Dec 2025 17:41:45 +0100 Subject: [PATCH 17/29] Change early out of query_subvolume_id to has_key Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Core/Main.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 01119d05..964a6797 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -4147,7 +4147,7 @@ public class Main : GLib.Object{ log_debug("query_subvolume_id():%s".printf(subvol_name)); // Early out when configured subvolume name != actual. - if(sys_subvolumes[root_subvolume_name] == null || sys_subvolumes[home_subvolume_name] == null) + if (!sys_subvolumes.has_key(root_subvolume_name) || !sys_subvolumes.has_key(home_subvolume_name)) return false; string cmd = ""; From 6a0240f2a71e97c3067c5770d598b0462513d9a3 Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 8 Dec 2025 18:12:42 +0100 Subject: [PATCH 18/29] Properly fix query_subvolume_id When root subvolume name is misconfigured to empty string, don't early out, but treat it the same as empty home subvolume name --- src/Core/Main.vala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 964a6797..0b14096d 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -4145,10 +4145,6 @@ public class Main : GLib.Object{ public bool query_subvolume_id(string subvol_name){ log_debug("query_subvolume_id():%s".printf(subvol_name)); - - // Early out when configured subvolume name != actual. - if (!sys_subvolumes.has_key(root_subvolume_name) || !sys_subvolumes.has_key(home_subvolume_name)) - return false; string cmd = ""; string std_out; @@ -4179,7 +4175,10 @@ public class Main : GLib.Object{ Subvolume subvol = null; - if ((sys_subvolumes.size > 0) && line.has_suffix(sys_subvolumes[root_subvolume_name].path.replace(repo.mount_paths[root_subvolume_name] + "/"," "))){ + if ((sys_subvolumes.size > 0) + && sys_subvolumes.has_key(root_subvolume_name) + && line.has_suffix(sys_subvolumes[root_subvolume_name].path.replace(repo.mount_paths[root_subvolume_name] + "/"," "))){ + subvol = sys_subvolumes[root_subvolume_name]; } else if ((sys_subvolumes.size > 0) From 216393b182d6e682b5aea857d53b02c0d1fb6ae1 Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 8 Dec 2025 18:22:42 +0100 Subject: [PATCH 19/29] Simplify vbox_subvolume_custom.visible assignment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Gtk/SnapshotBackendBox.vala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index 6b0b0762..ca893e99 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -262,9 +262,7 @@ class SnapshotBackendBox : Gtk.Box{ } public void update_custom_subvol_name_visibility() { - if(combo_subvol_layout.active == 0) - vbox_subvolume_custom.visible = true; - else vbox_subvolume_custom.visible = false; + vbox_subvolume_custom.visible = (combo_subvol_layout.active == 0); } private bool check_for_btrfs_tools() { From 59339bbd11932ba975f5b32c9ecfa3811d895077 Mon Sep 17 00:00:00 2001 From: fused0 Date: Mon, 8 Dec 2025 18:24:22 +0100 Subject: [PATCH 20/29] Fix typo in comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Gtk/SnapshotBackendBox.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index ca893e99..31eaa716 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -207,7 +207,7 @@ class SnapshotBackendBox : Gtk.Box{ list_store.get_value (iter, 1, out val2); App.home_subvolume_name = (string) val2; - // If home subolume name is empty, do not backup home. + // If home subvolume name is empty, do not backup home. if (App.home_subvolume_name == "") App.include_btrfs_home_for_backup = false; } From b32ac4e325334b59a5e014f308763533c6196de1 Mon Sep 17 00:00:00 2001 From: fused0 Date: Tue, 9 Dec 2025 09:53:17 +0100 Subject: [PATCH 21/29] Making sure to consistently call Main::check_btrfs_layout_system before snapshot create and restore actions --- src/Core/Main.vala | 11 +++++++++-- src/Gtk/MainWindow.vala | 8 ++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 0b14096d..dab5e0f7 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -515,7 +515,7 @@ public class Main : GLib.Object{ if (!supported){ string msg = _("The system partition has an unsupported subvolume layout.") + " "; - msg += _("Only ubuntu-type layouts with @ and @home subvolumes are currently supported.") + "\n\n"; + msg += _("Please mak sure you configured the subvolume layout correctly.") + "\n\n"; msg += _("Application will exit.") + "\n\n"; string title = _("Not Supported"); @@ -524,6 +524,7 @@ public class Main : GLib.Object{ gtk_messagebox(title, msg, win, true); } else{ + msg += _("Application will exit.") + "\n\n"; log_error(msg); } } @@ -2319,6 +2320,10 @@ public class Main : GLib.Object{ public bool restore_snapshot(Gtk.Window? parent_win){ log_debug("Main: restore_snapshot()"); + + if (btrfs_mode && (check_btrfs_layout_system() == false)){ + return false; + } parent_window = parent_win; @@ -4176,12 +4181,14 @@ public class Main : GLib.Object{ Subvolume subvol = null; if ((sys_subvolumes.size > 0) + && (root_subvolume_name != "") && sys_subvolumes.has_key(root_subvolume_name) && line.has_suffix(sys_subvolumes[root_subvolume_name].path.replace(repo.mount_paths[root_subvolume_name] + "/"," "))){ - + subvol = sys_subvolumes[root_subvolume_name]; } else if ((sys_subvolumes.size > 0) + && (home_subvolume_name != "") && sys_subvolumes.has_key(home_subvolume_name) && line.has_suffix(sys_subvolumes[home_subvolume_name].path.replace(repo.mount_paths[home_subvolume_name] + "/"," "))){ diff --git a/src/Gtk/MainWindow.vala b/src/Gtk/MainWindow.vala index cd127b1d..535cd4ec 100644 --- a/src/Gtk/MainWindow.vala +++ b/src/Gtk/MainWindow.vala @@ -760,6 +760,13 @@ class MainWindow : Gtk.Window{ private void restore(){ + + ui_sensitive(false); + + if (App.btrfs_mode && (App.check_btrfs_layout_system(this) == false)){ + ui_sensitive(true); + return; + } TreeIter iter; TreeSelection sel; @@ -827,6 +834,7 @@ class MainWindow : Gtk.Window{ App.dry_run = false; App.repo.load_snapshots(); refresh_all(); + ui_sensitive(true); }); } From 4b5cf13878e1b45cc29c596369ebd99ae17d30aa Mon Sep 17 00:00:00 2001 From: fused0 Date: Tue, 9 Dec 2025 10:30:43 +0100 Subject: [PATCH 22/29] fix null pointer exception in query_subvolume_quota in case the root subvolume is misconfigured to empty string --- src/Core/Main.vala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index dab5e0f7..7b2298e1 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -516,7 +516,6 @@ public class Main : GLib.Object{ if (!supported){ string msg = _("The system partition has an unsupported subvolume layout.") + " "; msg += _("Please mak sure you configured the subvolume layout correctly.") + "\n\n"; - msg += _("Application will exit.") + "\n\n"; string title = _("Not Supported"); if (app_mode == ""){ @@ -4277,11 +4276,15 @@ public class Main : GLib.Object{ Subvolume subvol = null; - if ((sys_subvolumes.size > 0) && (sys_subvolumes[root_subvolume_name].id == subvol_id)){ + if ((sys_subvolumes.size > 0) + && (root_subvolume_name != "") + && sys_subvolumes.has_key(root_subvolume_name) + && (sys_subvolumes[root_subvolume_name].id == subvol_id)){ subvol = sys_subvolumes[root_subvolume_name]; } else if ((sys_subvolumes.size > 0) + && (home_subvolume_name != "") && sys_subvolumes.has_key(home_subvolume_name) && (sys_subvolumes[home_subvolume_name].id == subvol_id)){ From 3ceb27d9eb4bab80ec58ae0cebcded3a318bc8ad Mon Sep 17 00:00:00 2001 From: fused0 Date: Wed, 10 Dec 2025 08:55:47 +0100 Subject: [PATCH 23/29] document and fix query_subvolume_id --- src/Core/Main.vala | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 7b2298e1..9d18e97a 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -2319,11 +2319,11 @@ public class Main : GLib.Object{ public bool restore_snapshot(Gtk.Window? parent_win){ log_debug("Main: restore_snapshot()"); - + if (btrfs_mode && (check_btrfs_layout_system() == false)){ return false; } - + parent_window = parent_win; // remove mount points which will remain on root fs @@ -4146,9 +4146,21 @@ public class Main : GLib.Object{ return ok; } + /** + * Queries the subvolume ID of a given subvolume by name. + * + * Subvolumes are listed by using ``btrfs subvolume list``. + * This function has a sideeffect, assigning the ID to the subvolume found in + * ``sys_subvolumes`` or ``repo.snapshots``. + * + * @return true if the subvolume was found. + */ public bool query_subvolume_id(string subvol_name){ log_debug("query_subvolume_id():%s".printf(subvol_name)); + + // Early out when subvol_name is empty. + if (subvol_name == "") return false; string cmd = ""; string std_out; @@ -4179,6 +4191,7 @@ public class Main : GLib.Object{ Subvolume subvol = null; + // Is the subvolume we are trying to ID the root subvolume? if ((sys_subvolumes.size > 0) && (root_subvolume_name != "") && sys_subvolumes.has_key(root_subvolume_name) @@ -4186,6 +4199,7 @@ public class Main : GLib.Object{ subvol = sys_subvolumes[root_subvolume_name]; } + // Or is it the home subvolume? else if ((sys_subvolumes.size > 0) && (home_subvolume_name != "") && sys_subvolumes.has_key(home_subvolume_name) @@ -4193,6 +4207,7 @@ public class Main : GLib.Object{ subvol = sys_subvolumes[home_subvolume_name]; } + // Otherwise, can we find the subvolume in snapshts? else { foreach(var bak in repo.snapshots){ foreach(var sub in bak.subvolumes.values){ @@ -4204,6 +4219,7 @@ public class Main : GLib.Object{ } } + // Assign the subvolume ID to the found subvolume. if (subvol != null){ subvol.id = long.parse(parts[1]); } From 4031b17767400cb9dfb83cf175e1d4a797273106 Mon Sep 17 00:00:00 2001 From: fused0 Date: Wed, 10 Dec 2025 12:01:43 +0100 Subject: [PATCH 24/29] document and fix query_subvolume_quota --- src/Core/Main.vala | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 9d18e97a..4b6448fc 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -4149,17 +4149,17 @@ public class Main : GLib.Object{ /** * Queries the subvolume ID of a given subvolume by name. * - * Subvolumes are listed by using ``btrfs subvolume list``. - * This function has a sideeffect, assigning the ID to the subvolume found in - * ``sys_subvolumes`` or ``repo.snapshots``. + * Subvolumes are listed using ``btrfs subvolume list``. + * This assigns the ID to the subvolume found in ``sys_subvolumes`` or + * ``repo.snapshots``. * - * @return true if the subvolume was found. + * @return true if no error occured. */ public bool query_subvolume_id(string subvol_name){ log_debug("query_subvolume_id():%s".printf(subvol_name)); - // Early out when subvol_name is empty. + // Check validity of arguments, querying empty string is an error. if (subvol_name == "") return false; string cmd = ""; @@ -4236,9 +4236,21 @@ public class Main : GLib.Object{ return ok; } + /** + * Queries the subvolume quotas of a given subvolume by name. + * + * Subvolumes qoatas are listed using ``btrfs qgroup show``. + * This function assigns the quotas to the subvolume found in ``sys_subvolumes`` + * or ``repo.snapshots``. + * + * @return true if no error occured. + */ public bool query_subvolume_quota(string subvol_name){ log_debug("query_subvolume_quota():%s".printf(subvol_name)); + // Check validity of arguments, querying empty string is an error. + if (subvol_name == "") return false; + string cmd = ""; string std_out; string std_err; @@ -4292,6 +4304,7 @@ public class Main : GLib.Object{ Subvolume subvol = null; + // Is the subvolume we are trying to get quotas for the root subvolume? if ((sys_subvolumes.size > 0) && (root_subvolume_name != "") && sys_subvolumes.has_key(root_subvolume_name) @@ -4299,6 +4312,7 @@ public class Main : GLib.Object{ subvol = sys_subvolumes[root_subvolume_name]; } + // Or is it the home subvolume? else if ((sys_subvolumes.size > 0) && (home_subvolume_name != "") && sys_subvolumes.has_key(home_subvolume_name) @@ -4306,6 +4320,7 @@ public class Main : GLib.Object{ subvol = sys_subvolumes[home_subvolume_name]; } + // Otherwise, can we find the subvolume in snapshts? else { foreach(var bak in repo.snapshots){ foreach(var sub in bak.subvolumes.values){ @@ -4316,6 +4331,7 @@ public class Main : GLib.Object{ } } + // Assign the subvolume quotas to the found subvolume. if (subvol != null){ int part_num = -1; foreach(string part in parts){ From 4630806c38f757a2d160f8b0771104c8588db9c6 Mon Sep 17 00:00:00 2001 From: fused0 Date: Tue, 16 Dec 2025 10:36:06 +0100 Subject: [PATCH 25/29] Added comments to SnapshotBackendBox --- src/Gtk/SnapshotBackendBox.vala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Gtk/SnapshotBackendBox.vala b/src/Gtk/SnapshotBackendBox.vala index 31eaa716..1cdb5e8d 100644 --- a/src/Gtk/SnapshotBackendBox.vala +++ b/src/Gtk/SnapshotBackendBox.vala @@ -118,6 +118,9 @@ class SnapshotBackendBox : Gtk.Box{ }); } + /** + * Creates BTRFS subvolume selection UI + */ private void create_btrfs_subvolume_selection(Gtk.Box vbox) { // subvolume layout @@ -208,6 +211,9 @@ class SnapshotBackendBox : Gtk.Box{ App.home_subvolume_name = (string) val2; // If home subvolume name is empty, do not backup home. + // Unfortunately, due to how the settings and wizard dialogs work + // (changing the settings immediately), this might opaquely change + // the setting for the user to not include home subvolume in backups. if (App.home_subvolume_name == "") App.include_btrfs_home_for_backup = false; } From c6cd723853598f932c760c3c23ccbc491060ddc2 Mon Sep 17 00:00:00 2001 From: fused0 Date: Tue, 6 Jan 2026 11:30:47 +0100 Subject: [PATCH 26/29] Main: add check_btrfs_system_config to check if the BTRFS system config is valid Main: change check_btrfs_volume to take an array of subvolume names SnapshotRepo: get rid of default mount_paths SnapshotRepo: add check_config to validate btrfs config and give appropriate error messages --- src/Core/Main.vala | 110 ++++++++++++++++++++++++-------- src/Core/SnapshotRepo.vala | 125 +++++++++++++++++++++++++------------ 2 files changed, 170 insertions(+), 65 deletions(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 4b6448fc..d7684be4 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -503,34 +503,85 @@ public class Main : GLib.Object{ log_msg(_("** Uninstalled Timeshift BTRFS **")); } } - - public bool check_btrfs_layout_system(Gtk.Window? win = null){ - log_debug("check_btrfs_layout_system()"); + /* + * Checks if root_subvolume_name and home_subvolume_name are configured to valid values. + * + * @return False if the config is invalid. + */ + public bool check_btrfs_system_config(out string title, out string msg) { - bool supported = sys_subvolumes.has_key(root_subvolume_name); - if (include_btrfs_home_for_backup){ - supported = supported && sys_subvolumes.has_key(home_subvolume_name); + log_debug("check_btrfs_system_config()"); + + // If the root subvolume is configured to an empty string, the config is invalid. + if(root_subvolume_name == "") { + title = _("Root subvolume configuration is invalid"); + msg = _("Root subvolume name is empty, make sure to select a valid subvolume layout."); + return false; } - if (!supported){ - string msg = _("The system partition has an unsupported subvolume layout.") + " "; - msg += _("Please mak sure you configured the subvolume layout correctly.") + "\n\n"; - string title = _("Not Supported"); - + // If the home subvolume is configured to an empty string and home backup is + // enabbled, the config is invalid. + if (include_btrfs_home_for_backup && home_subvolume_name == "") { + title = _("Home subvolume configuration is invalid"); + msg = _("Home backups are enabled home subvolume name is empty, make sure to select a valid subvolume layout."); + return false; + } + + // If sys_subvolumes does not contain a subvolume for root_subvolume_name, + // the config is invalid. + if (!sys_subvolumes.has_key(root_subvolume_name)) { + title = _("Root subvolume configuration is invalid"); + msg = _("The configured root subvolume does not exist") + " (" + root_subvolume_name + ")."; + return false; + } + + // If home backups are enbaled and sys_subvolumes does not contain a subvolume + // for home_subvolume_name, the config is invalid. + if (include_btrfs_home_for_backup && !sys_subvolumes.has_key(home_subvolume_name)) { + title = _("Home subvolume configuration is invalid"); + msg = _("Home backups are enabled and the configured home subvolume does not exist") + " (" + home_subvolume_name + ")."; + return false; + } + + return true; + } + + /* + * Calls check_btrfs_system_config and displays an error message to the user + * + * @return False if the config is invalid. + */ + public bool check_btrfs_layout_system(Gtk.Window? win = null) { + + log_debug("check_btrfs_layout_system()"); + + // Checking for failure conditions + string title; + string msg; + if(!check_btrfs_system_config(out title, out msg)) { if (app_mode == ""){ gtk_set_busy(false, win); gtk_messagebox(title, msg, win, true); } else{ - msg += _("Application will exit.") + "\n\n"; - log_error(msg); + msg += "\n\n" + _("Application will exit.") + "\n\n"; + log_error(title + "\n\n" + msg); } + + return false; } - return supported; + return true; } + /* + * Checks if the root and home devices are btrfs filesystems and does some + * further checking if the device fs contains the requested suvolume names + * with check_btrfs_volume(). + * + * @return True if the layout is supported. + */ public bool check_btrfs_layout(Device? dev_root, Device? dev_home, bool unlock){ bool supported = true; // keep true for non-btrfs systems @@ -541,18 +592,18 @@ public class Main : GLib.Object{ if (dev_home != dev_root){ - supported = supported && check_btrfs_volume(dev_root, root_subvolume_name, unlock); + supported = supported && check_btrfs_volume(dev_root, {root_subvolume_name}, unlock); if (include_btrfs_home_for_backup){ - supported = supported && check_btrfs_volume(dev_home, home_subvolume_name, unlock); + supported = supported && check_btrfs_volume(dev_home, {home_subvolume_name}, unlock); } } else{ if (include_btrfs_home_for_backup){ - supported = supported && check_btrfs_volume(dev_root, "@,@home", unlock); + supported = supported && check_btrfs_volume(dev_root, {root_subvolume_name, home_subvolume_name}, unlock); } else{ - supported = supported && check_btrfs_volume(dev_root, root_subvolume_name, unlock); + supported = supported && check_btrfs_volume(dev_root, {root_subvolume_name}, unlock); } } } @@ -3914,9 +3965,18 @@ public class Main : GLib.Object{ return repo.status_code; } - public bool check_btrfs_volume(Device dev, string subvol_names, bool unlock){ + /* + * Checks if device has subvolumes listed in subvol_names. + * Mounts the device if it's not mounted. Device is unmounted in the end. + * + * @return True if device has subvolumes. + */ + public bool check_btrfs_volume(Device dev, string[] subvol_names, bool unlock){ - log_debug("check_btrfs_volume():%s".printf(subvol_names)); + log_debug("check_btrfs_volume()"); + foreach(string subvol_name in subvol_names) { + log_debug("-- " + subvol_name); + } string mnt_btrfs = mount_point_app + "/btrfs"; dir_create(mnt_btrfs); @@ -3936,7 +3996,7 @@ public class Main : GLib.Object{ if (dev_unlocked == null){ log_debug("device is null"); - log_debug("SnapshotRepo: check_btrfs_volume(): exit"); + log_debug("Main: check_btrfs_volume(): exit"); return false; } else{ @@ -3954,7 +4014,7 @@ public class Main : GLib.Object{ bool supported = true; - foreach(string subvol_name in subvol_names.split(",")){ + foreach(string subvol_name in subvol_names){ supported = supported && dir_exists(path_combine(mnt_btrfs,subvol_name)); } @@ -4013,7 +4073,7 @@ public class Main : GLib.Object{ if (dev.has_children()) { return false; } if (btrfs_mode && ((dev.fstype == "btrfs")||(dev.fstype == "luks"))){ - if (check_btrfs_volume(dev, root_subvolume_name, unlock)){ + if (check_btrfs_volume(dev, {root_subvolume_name}, unlock)){ return true; } } @@ -4157,7 +4217,7 @@ public class Main : GLib.Object{ */ public bool query_subvolume_id(string subvol_name){ - log_debug("query_subvolume_id():%s".printf(subvol_name)); + log_debug("query_subvolume_id(): \"%s\"".printf(subvol_name)); // Check validity of arguments, querying empty string is an error. if (subvol_name == "") return false; @@ -4246,7 +4306,7 @@ public class Main : GLib.Object{ * @return true if no error occured. */ public bool query_subvolume_quota(string subvol_name){ - log_debug("query_subvolume_quota():%s".printf(subvol_name)); + log_debug("query_subvolume_quota(): \"%s\"".printf(subvol_name)); // Check validity of arguments, querying empty string is an error. if (subvol_name == "") return false; diff --git a/src/Core/SnapshotRepo.vala b/src/Core/SnapshotRepo.vala index 650a8215..a7203a94 100644 --- a/src/Core/SnapshotRepo.vala +++ b/src/Core/SnapshotRepo.vala @@ -191,22 +191,22 @@ public class SnapshotRepo : GLib.Object{ if (mount_path.length == 0){ return false; } - - // rsync - mount_paths[App.root_subvolume_name] = ""; - mount_paths[App.home_subvolume_name] = ""; if (btrfs_mode){ - + + if (App.root_subvolume_name == "") return false; + + // Don't add mount paths if root_subvolume_name or home_subvolume_name are empty. + // We don't add default values either anymore, because it could lead us to bugs. mount_paths[App.root_subvolume_name] = mount_path; - mount_paths[App.home_subvolume_name] = mount_path; //default + if (App.home_subvolume_name != "") mount_paths[App.home_subvolume_name] = mount_path; device_home = device; //default // mount @home if on different disk ------- var repo_subvolumes = Subvolume.detect_subvolumes_for_system_by_path(path_combine(mount_path,App.root_subvolume_name), this, parent_window); - if (repo_subvolumes.has_key(App.home_subvolume_name)){ + if (App.home_subvolume_name != "" && repo_subvolumes.has_key(App.home_subvolume_name)){ var subvol = repo_subvolumes[App.home_subvolume_name]; @@ -469,51 +469,96 @@ public class SnapshotRepo : GLib.Object{ //log_debug("checking selected device"); + // Snapshot repo is available if the config is valid. + bool ok = check_config(out status_message, out status_details, out status_code); + + if (ok){ + log_debug(status_message); + log_debug("is_available: ok"); + } else { + log_debug(status_message); + log_debug("is_available: false"); + } + + return ok; + } + + /* + * Validates the btrfs config and displays an appropriate error message for the user. + * It's a little convoluted because it needs to catch any misconfiguration leading + * to error states. + */ + private bool check_config(out string title, out string msg, out SnapshotLocationStatus code){ + + log_debug("SnapshotRepo: check_btrfs_config()"); + + log_debug("btrfs_mode=%s".printf(btrfs_mode.to_string())); + if (device == null){ if (App.backup_uuid == null || App.backup_uuid.length == 0){ log_debug("device is null"); - status_message = _("Snapshot device not selected"); - status_details = _("Select the snapshot device"); - status_code = SnapshotLocationStatus.NOT_SELECTED; - log_debug("is_available: false"); + title = _("Snapshot device not selected"); + msg = _("Select the snapshot device"); + code = SnapshotLocationStatus.NOT_SELECTED; return false; } else{ - status_message = _("Snapshot device not available"); - status_details = _("Device not found") + ": UUID='%s'".printf(App.backup_uuid); - status_code = SnapshotLocationStatus.NOT_AVAILABLE; - log_debug("is_available: false"); + title = _("Snapshot device not available"); + msg = _("Device not found") + ": UUID='%s'".printf(App.backup_uuid); + code = SnapshotLocationStatus.NOT_AVAILABLE; return false; } } - else{ - if (btrfs_mode){ - bool ok = has_btrfs_system(); - if (ok){ - log_debug("is_available: ok"); - } - return ok; - } - else{ - log_debug("is_available: ok"); - return true; + + if (btrfs_mode) { + // Run the btrfs checks from Main.check_btrfs_system_config. + if (!App.check_btrfs_system_config(out title, out msg)) { + code = SnapshotLocationStatus.NO_BTRFS_SYSTEM; + return false; } - } - } - public bool has_btrfs_system(){ - - log_debug("SnapshotRepo: has_btrfs_system()"); + // Run some additional checks, these cases are unlikely to come up, + // but they could point to bugs in SnapshotRepo code or any other + // code related to configuring the system layout. + // Bear with me, even though it seems confusing at first... - var root_path = path_combine(mount_paths[App.root_subvolume_name],App.root_subvolume_name); - log_debug("root_path=%s".printf(root_path)); - log_debug("btrfs_mode=%s".printf(btrfs_mode.to_string())); - if (btrfs_mode){ - if (!dir_exists(root_path)){ - status_message = _("Selected snapshot device is not a system disk"); - status_details = _("Select BTRFS system disk with root subvolume (@)"); - status_code = SnapshotLocationStatus.NO_BTRFS_SYSTEM; - log_debug(status_message); + // Check the home subvolume configuration. + // home_path can be null if mount_paths[App.home_subvolume_name] doesn't exist. + var home_path = path_combine(mount_paths[App.home_subvolume_name], App.home_subvolume_name); + var home_subvolume_configured = App.home_subvolume_name != ""; + var has_home_mount_path = mount_paths.has_key(App.home_subvolume_name); + + log_debug("home_path=%s".printf(home_path)); + log_debug("home_subvolume_configured=%s".printf(home_subvolume_configured.to_string())); + log_debug("has_home_mount_path=%s".printf(has_home_mount_path.to_string())); + + // If home_subvolume_configured and the directory does not exists, the + // configuration is invalid. + if (!has_home_mount_path || (home_subvolume_configured && !dir_exists(home_path))) { + title = _("Home subvolume configuration is invalid"); + msg = _("Home subvolume is configured but the path does not exist") + " (" + App.home_subvolume_name + ")"; + code = SnapshotLocationStatus.NO_BTRFS_SYSTEM; + return false; + } + + // Further check root subvolume configuration. + // We already know App.root_subvolume_name is not empty, but we still need to check if + // the key exists (it might not if there is no mount path associated to it). + var has_root_mount_path = mount_paths.has_key(App.root_subvolume_name); + // root_path can still be null if has_root_mount_path is false. + var root_path = path_combine(mount_paths[App.root_subvolume_name], App.root_subvolume_name); + + log_debug("root_path=%s".printf(root_path)); + log_debug("has_root_mount_path=%s".printf(has_root_mount_path.to_string())); + + // If we don't have a mount_path for root, or root_path is null, or the root_path + // directory doesn't exist, the configuration is invalid. + // Technically, we don't need to explicitly check root_path here, it's implied by + // !has_root_mount_path. + if (!has_root_mount_path || !dir_exists(root_path)){ + title = _("Root subvolume configuration is invalid"); + msg = _("Root subvolume is configured but the path does not exist. Select BTRFS system disk with root subvolume ") + " (" + App.home_subvolume_name + ")"; + code = SnapshotLocationStatus.NO_BTRFS_SYSTEM; return false; } } From 0611f62b2f181c5758b6e821bbb8e7db68e37dc3 Mon Sep 17 00:00:00 2001 From: fused0 Date: Tue, 6 Jan 2026 11:31:33 +0100 Subject: [PATCH 27/29] AppConsole: make sure App.check_btrfs_layout_system is called before any action taken --- src/AppConsole.vala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/AppConsole.vala b/src/AppConsole.vala index d5ddb0bc..90bda014 100644 --- a/src/AppConsole.vala +++ b/src/AppConsole.vala @@ -618,6 +618,12 @@ public class AppConsole : GLib.Object { private bool restore_snapshot(){ + // It's also called by App.restore_snapshot, but might leave some temp directories + // if layout is unsupported and not checked before. + if (App.btrfs_mode && (App.check_btrfs_layout_system() == false)){ + return false; + } + select_snapshot_device(true); select_snapshot_for_restore(); @@ -1354,6 +1360,10 @@ public class AppConsole : GLib.Object { public bool delete_snapshot(){ + if (App.btrfs_mode && (App.check_btrfs_layout_system() == false)){ + return false; + } + select_snapshot_device(true); select_snapshot_for_deletion(); @@ -1366,6 +1376,10 @@ public class AppConsole : GLib.Object { } public bool delete_all_snapshots(){ + + if (App.btrfs_mode && (App.check_btrfs_layout_system() == false)){ + return false; + } select_snapshot_device(true); From 322836216d2a05e62844cb59982a11c30ced3032 Mon Sep 17 00:00:00 2001 From: fused0 Date: Fri, 9 Jan 2026 11:35:55 +0100 Subject: [PATCH 28/29] Fix more occurences of @ and @home in: * Mounting of btrfs devices * UI lables * Log messages and errors * Parsing of fstab and crypttab --- src/Core/Main.vala | 26 ++++++++++++++------------ src/Core/Snapshot.vala | 6 +++--- src/Gtk/UsersBox.vala | 4 ++-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/Core/Main.vala b/src/Core/Main.vala index 5d31e1e1..6e138889 100644 --- a/src/Core/Main.vala +++ b/src/Core/Main.vala @@ -1828,11 +1828,13 @@ public class Main : GLib.Object{ string dst_path = path_combine(snapshot_path, subvol_name); // Dirty hack to fix the nested subvilumes issue (cause of issue is unknown) - if (dst_path.has_suffix("/@/@")){ - dst_path = dst_path.replace("/@/@", "/@"); + string nested_root_subvol = @"/$(root_subvolume_name)/$(root_subvolume_name)"; + string nested_home_subvol = @"/$(home_subvolume_name)/$(home_subvolume_name)"; + if (dst_path.has_suffix(nested_root_subvol)){ + dst_path = dst_path.replace(nested_root_subvol, @"/$(root_subvolume_name)"); } - else if (dst_path.has_suffix("/@home/@home")){ - dst_path = dst_path.replace("/@home/@home", "/@home"); + else if (dst_path.has_suffix(nested_home_subvol)){ + dst_path = dst_path.replace(nested_home_subvol, @"/$(home_subvolume_name)"); } string cmd = "btrfs subvolume snapshot '%s' '%s' \n".printf(src_path, dst_path); @@ -2420,11 +2422,11 @@ public class Main : GLib.Object{ if (btrfs_mode){ if (repo.mount_paths[root_subvolume_name].length == 0){ - log_error(_("BTRFS device is not mounted") + ": @"); + log_error(_("BTRFS device is not mounted") + ": " + root_subvolume_name); return false; } if (include_btrfs_home_for_restore && (repo.mount_paths[home_subvolume_name].length == 0)){ - log_error(_("BTRFS device is not mounted") + ": @home"); + log_error(_("BTRFS device is not mounted") + ": " + home_subvolume_name); return false; } } @@ -3281,9 +3283,9 @@ public class Main : GLib.Object{ string snapshot_path = ""; /* Note: - * The @ and @home subvolumes need to be backed-up only if they are in use by the system. + * The root and home subvolumes need to be backed-up only if they are in use by the system. * If user restores a snapshot and then tries to restore another snapshot before the next reboot - * then the @ and @home subvolumes are the ones that were previously restored and need to be deleted. + * then the root and home subvolumes are the ones that were previously restored and need to be deleted. * */ bool create_pre_restore_backup = false; @@ -3307,11 +3309,11 @@ public class Main : GLib.Object{ //delete system subvolumes if (sys_subvolumes.has_key(root_subvolume_name) && snapshot_to_restore.subvolumes.has_key(root_subvolume_name)){ sys_subvolumes[root_subvolume_name].remove(); - log_msg(_("Deleted subvolume") + ": @"); + log_msg(_("Deleted subvolume") + ": " + root_subvolume_name); } if (include_btrfs_home_for_restore && sys_subvolumes.has_key(home_subvolume_name) && snapshot_to_restore.subvolumes.has_key(home_subvolume_name)){ sys_subvolumes[home_subvolume_name].remove(); - log_msg(_("Deleted subvolume") + ": @home"); + log_msg(_("Deleted subvolume") + ": " + home_subvolume_name); } //update description for pre-restore backup @@ -3911,11 +3913,11 @@ public class Main : GLib.Object{ if (mnt.device.fstype == "btrfs"){ if (mnt.mount_point == "/"){ - mount_options = "subvol=@"; + mount_options = "subvol=" + root_subvolume_name; } if (include_btrfs_home_for_restore){ if (mnt.mount_point == "/home"){ - mount_options = "subvol=@home"; + mount_options = "subvol=" + home_subvolume_name; } } } diff --git a/src/Core/Snapshot.vala b/src/Core/Snapshot.vala index 32880f02..a37758c4 100644 --- a/src/Core/Snapshot.vala +++ b/src/Core/Snapshot.vala @@ -321,7 +321,7 @@ public class Snapshot : GLib.Object{ string fstab_path = path_combine(path, "/localhost/etc/fstab"); if (btrfs_mode){ - fstab_path = path_combine(path, "/@/etc/fstab"); + fstab_path = path_combine(path, @"/$(App.root_subvolume_name)/etc/fstab"); } fstab_list = FsTabEntry.read_file(fstab_path); @@ -332,7 +332,7 @@ public class Snapshot : GLib.Object{ string crypttab_path = path_combine(path, "/localhost/etc/crypttab"); if (btrfs_mode){ - crypttab_path = path_combine(path, "/@/etc/crypttab"); + crypttab_path = path_combine(path, @"/$(App.root_subvolume_name)/etc/crypttab"); } cryttab_list = CryptTabEntry.read_file(crypttab_path); @@ -439,7 +439,7 @@ public class Snapshot : GLib.Object{ public bool has_subvolumes(){ foreach(FsTabEntry en in fstab_list){ - if (en.options.contains("subvol=@")){ + if (en.options.contains("subvol=" + App.root_subvolume_name)){ return true; } } diff --git a/src/Gtk/UsersBox.vala b/src/Gtk/UsersBox.vala index 00c1cd05..177d3edf 100644 --- a/src/Gtk/UsersBox.vala +++ b/src/Gtk/UsersBox.vala @@ -322,7 +322,7 @@ class UsersBox : Gtk.Box{ if (restore_mode){ - chk_include_btrfs_home = new Gtk.CheckButton.with_label(_("Restore @home subvolume")); + chk_include_btrfs_home = new Gtk.CheckButton.with_label(_("Restore home subvolume")); box.add(chk_include_btrfs_home); @@ -333,7 +333,7 @@ class UsersBox : Gtk.Box{ } else { - chk_include_btrfs_home = new Gtk.CheckButton.with_label(_("Include @home subvolume in backups")); + chk_include_btrfs_home = new Gtk.CheckButton.with_label(_("Include home subvolume in backups")); box.add(chk_include_btrfs_home); From aefb4e40345a3c809a7b8dfdec831810bd1e61ce Mon Sep 17 00:00:00 2001 From: fused0 Date: Fri, 9 Jan 2026 13:32:11 +0100 Subject: [PATCH 29/29] AppConsole: Don't call check_btrfs_layout_system before device selection --- src/AppConsole.vala | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/AppConsole.vala b/src/AppConsole.vala index 0afe4beb..54c2006d 100644 --- a/src/AppConsole.vala +++ b/src/AppConsole.vala @@ -619,12 +619,6 @@ public class AppConsole : GLib.Object { private bool restore_snapshot(){ - // It's also called by App.restore_snapshot, but might leave some temp directories - // if layout is unsupported and not checked before. - if (App.btrfs_mode && (App.check_btrfs_layout_system() == false)){ - return false; - } - select_snapshot_device(true); select_snapshot_for_restore();