From 4d528dacec544782035ae13f6afd2e5c195fc8f2 Mon Sep 17 00:00:00 2001 From: fused01 Date: Sun, 23 Nov 2025 15:53:19 +0100 Subject: [PATCH 01/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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)){