From 686e4a9567b2e5dba1cc37257ba657d1e9265831 Mon Sep 17 00:00:00 2001 From: Kacper Paczos Date: Tue, 25 Aug 2026 21:54:18 +0200 Subject: [PATCH 1/2] feat: named profiles for the home directory a box is given Creating a box already accepts a custom home directory, but it is a bare path field: nothing says what it changes, and anyone using it for more than one box has to remember the paths and retype them. A profile is that directory with a name on it. The primary menu gains a Profiles window listing them, each with the directory it points at, a Browse button that opens it in the file manager, and Remove. Adding one takes a name and puts the directory under ~/boxes; the directory itself is created when it is first needed rather than up front, since distrobox makes it when a box is built and Browse makes it when someone looks. The new-box form gets a Profile combo listing "Host (shared home)" first, then the profiles. Picking one fills in the home path; picking Host clears it, which is distrobox's own default and what every existing box uses. The folder picker still works for a one-off path, and the value handed to distrobox is unchanged - so this is a way of choosing the existing option, not a new mechanism. Profiles live in GSettings as a name-to-path map, the way exported-app labels do. Removing one forgets the setting only: the directory and any box already built on it are left alone. --- io.github.dvlv.boxbuddyrs.gschema.xml | 8 + src/main.rs | 202 ++++++++++++++++++++++++-- src/utils.rs | 94 +++++++++++- 3 files changed, 291 insertions(+), 13 deletions(-) diff --git a/io.github.dvlv.boxbuddyrs.gschema.xml b/io.github.dvlv.boxbuddyrs.gschema.xml index 19e8785..39ee0d3 100644 --- a/io.github.dvlv.boxbuddyrs.gschema.xml +++ b/io.github.dvlv.boxbuddyrs.gschema.xml @@ -8,5 +8,13 @@ The terminal which should be checked for first when performing an action which spawns a terminal window. + + {} + Named home directories for boxes + + Maps a profile name to the home directory boxes using it are given. A box + with no profile uses the host's home, which is distrobox's default. + + diff --git a/src/main.rs b/src/main.rs index a20908a..49f16d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,9 @@ use std::path::Path; use std::thread; use adw::{ - prelude::{ActionRowExt, MessageDialogExt, PreferencesGroupExt, PreferencesRowExt}, + prelude::{ + ActionRowExt, ComboRowExt, MessageDialogExt, PreferencesGroupExt, PreferencesRowExt, + }, ActionRow, Application, StyleManager, ToastOverlay, }; use gtk::{ @@ -27,13 +29,14 @@ use distrobox_handler::{ mod utils; use utils::{ get_assemble_icon, get_available_app_icon_name, get_available_icon_name, get_cpu_and_mem_usage, - get_deb_distros, get_distro_img, get_download_dir_path, get_my_deb_boxes, get_my_rpm_boxes, - get_rpm_distros, get_supported_terminals, get_supported_terminals_list, - get_terminal_and_separator_arg, has_distrobox_installed, has_file_extension, has_host_access, - has_podman_or_docker_installed, set_up_localisation, ADD_ICON_NAMES, APPLICATIONS_ICON_NAMES, - ASSEMBLE_FALLBACK_ICON_NAMES, COPY_ICON_NAMES, INFO_ICON_NAMES, INSTALL_PACKAGE_ICON_NAMES, - MENU_ICON_NAMES, OPEN_FILE_ICON_NAMES, REMOVE_ICON_NAMES, STOP_ICON_NAMES, TERMINAL_ICON_NAMES, - TRASH_ICON_NAMES, UPGRADE_ICON_NAMES, WARNING_ICON_NAMES, + get_deb_distros, get_distro_img, get_download_dir_path, get_host_home_dir, get_my_deb_boxes, + get_my_rpm_boxes, get_profiles, get_rpm_distros, get_supported_terminals, + get_supported_terminals_list, get_terminal_and_separator_arg, has_distrobox_installed, + has_file_extension, has_host_access, has_podman_or_docker_installed, open_path_in_file_manager, + remove_profile, set_profile, set_up_localisation, valid_profile_name, ADD_ICON_NAMES, + APPLICATIONS_ICON_NAMES, ASSEMBLE_FALLBACK_ICON_NAMES, COPY_ICON_NAMES, INFO_ICON_NAMES, + INSTALL_PACKAGE_ICON_NAMES, MENU_ICON_NAMES, OPEN_FILE_ICON_NAMES, REMOVE_ICON_NAMES, + STOP_ICON_NAMES, TERMINAL_ICON_NAMES, TRASH_ICON_NAMES, UPGRADE_ICON_NAMES, WARNING_ICON_NAMES, }; const APP_ID: &str = "io.github.dvlv.boxbuddyrs"; @@ -279,11 +282,18 @@ fn set_window_actions(window: &ApplicationWindow) { }) .build(); + let action_show_profiles = gio::ActionEntry::builder("show_profiles") + .activate(|window: &ApplicationWindow, _, _| { + show_profiles_popup(window); + }) + .build(); + window.add_action_entries([ action_refresh, action_about, action_close, action_set_preferred_terminal, + action_show_profiles, ]); } @@ -306,11 +316,19 @@ fn get_main_menu_model() -> gio::MenuModel { ); menu.insert_item( 2, + &gio::MenuItem::new( + //TRANSLATORS: Menu Item + Some(&gettext("Profiles")), + Some("win.show_profiles"), + ), + ); + menu.insert_item( + 3, //TRANSLATORS: Menu Item &gio::MenuItem::new(Some(&gettext("About BoxBuddy")), Some("win.about")), ); menu.insert_item( - 3, + 4, //TRANSLATORS: Menu Item &gio::MenuItem::new(Some(&gettext("Quit")), Some("win.close")), ); @@ -895,7 +913,7 @@ fn create_new_distrobox(window: &ApplicationWindow) { // TRANSLATORS: Entry Label - Name input for new distrobox name_entry_row.set_title(&gettext("Name")); - // custom home + // Profile selection let choose_home_btn = gtk::Button::from_icon_name(&get_available_icon_name(OPEN_FILE_ICON_NAMES)); choose_home_btn.set_margin_top(10); @@ -904,9 +922,10 @@ fn create_new_distrobox(window: &ApplicationWindow) { home_select_row.set_activatable_widget(Some(&choose_home_btn)); home_select_row.add_suffix(&choose_home_btn); - //home entry row for manual edit + // home entry row for manual edit / custom path (kept insensitive, driven by combo) let home_entry_row = adw::EntryRow::new(); home_entry_row.set_hexpand(true); + home_entry_row.set_sensitive(false); //Additional Volumes - will not be shown without host access let volume_box_list = gtk::ListBox::new(); @@ -920,8 +939,9 @@ fn create_new_distrobox(window: &ApplicationWindow) { home_select_row.add_prefix(&home_entry_row); let home_entry_row_future_clone = home_entry_row.clone(); + let home_row_for_picker = home_entry_row.clone(); choose_home_btn.connect_clicked(clone!(@weak window => move |_btn| { - let home_clone = home_entry_row.clone(); + let home_clone = home_row_for_picker.clone(); let file_dialog = FileDialog::builder().modal(false).build(); file_dialog.select_folder(Some(&window), None::<&gio::Cancellable>, clone!(@weak window => move |result| { if let Ok(file) = result { @@ -931,6 +951,37 @@ fn create_new_distrobox(window: &ApplicationWindow) { })); })); + // Profile combo row + let profiles = get_profiles(); + let mut profile_names = vec![gettext("Host (shared home)")]; + for (name, _path) in &profiles { + profile_names.push(name.clone()); + } + let profile_strlist = gtk::StringList::new( + &profile_names + .iter() + .map(|s| s.as_str()) + .collect::>(), + ); + + let profile_combo = adw::ComboRow::new(); + profile_combo.set_title(&gettext("Profile")); + profile_combo.set_model(Some(&profile_strlist)); + profile_combo.set_selected(0); + + let profile_combo_clone = profile_combo.clone(); + let home_entry_row_combo_clone = home_entry_row.clone(); + let profiles_clone = profiles.clone(); + profile_combo.connect_selected_item_notify(move |_combo| { + let selected = profile_combo_clone.selected(); + if selected == 0 { + // "Host (shared home)" - empty path + home_entry_row_combo_clone.set_text(""); + } else if let Some((_name, path)) = profiles_clone.get((selected - 1) as usize) { + home_entry_row_combo_clone.set_text(path); + } + }); + // hostname let hostname_entry_row = adw::EntryRow::new(); hostname_entry_row.set_hexpand(true); @@ -1075,6 +1126,7 @@ fn create_new_distrobox(window: &ApplicationWindow) { boxed_list.append(&image_select_row); boxed_list.append(&init_row); + boxed_list.append(&profile_combo); boxed_list.append(&home_select_row); boxed_list.append(&hostname_entry_row); @@ -2081,3 +2133,129 @@ fn show_preferred_terminal_popup(window: &ApplicationWindow) { term_pref_popup.set_child(Some(&main_box)); term_pref_popup.present(); } + +/// One row of the profiles list: its name, the directory boxes using it get, +/// a button to look at that directory in the file manager, and one to forget +/// the profile. Removing it only forgets the setting - the directory and any +/// box already built on it are left alone. +fn add_profile_row(group: &adw::PreferencesGroup, name: &str, path: &str) { + let row = adw::ActionRow::new(); + row.set_title(name); + row.set_subtitle(path); + + // TRANSLATORS: Button Label - opens the profile's folder in the file manager + let browse_btn = gtk::Button::with_label(&gettext("Browse")); + browse_btn.set_valign(Align::Center); + let browse_path = path.to_string(); + browse_btn.connect_clicked(move |_btn| { + open_path_in_file_manager(&browse_path); + }); + row.add_suffix(&browse_btn); + + // TRANSLATORS: Button Label + let remove_btn = gtk::Button::with_label(&gettext("Remove")); + remove_btn.set_valign(Align::Center); + let name_clone = name.to_string(); + let row_clone = row.clone(); + let group_clone = group.clone(); + remove_btn.connect_clicked(move |_btn| { + remove_profile(&name_clone); + group_clone.remove(&row_clone); + }); + row.add_suffix(&remove_btn); + + group.add(&row); +} + +fn show_profiles_popup(window: &ApplicationWindow) { + let profiles_popup = gtk::Window::builder() + // TRANSLATORS: Popup Window Title + .title(gettext("Profiles")) + .transient_for(window) + .default_width(600) + .default_height(400) + .modal(true) + .build(); + + // TRANSLATORS: Button Label + let close_btn = gtk::Button::with_label(&gettext("Close")); + close_btn.add_css_class("suggested-action"); + close_btn.connect_clicked(move |btn| { + let win = btn.root().and_downcast::().unwrap(); + win.destroy(); + }); + + let profiles_titlebar = adw::HeaderBar::new(); + profiles_titlebar.set_show_end_title_buttons(false); + profiles_titlebar.pack_end(&close_btn); + + profiles_popup.set_titlebar(Some(&profiles_titlebar)); + + let main_box = gtk::Box::new(Orientation::Vertical, 10); + main_box.set_margin_start(10); + main_box.set_margin_end(10); + main_box.set_margin_top(10); + main_box.set_margin_bottom(10); + + // TRANSLATORS: Dialog heading + let heading = gtk::Label::new(Some(&gettext("Profiles"))); + heading.add_css_class("title-1"); + heading.set_xalign(0.0); + + // TRANSLATORS: Dialog body text + let body = gtk::Label::new(Some(&gettext( + "A profile gives a box its own home directory, so applications keep separate settings and logins. Boxes with no profile use your host home.", + ))); + body.set_xalign(0.0); + body.set_wrap(true); + body.set_wrap_mode(gtk::pango::WrapMode::WordChar); + + let prefs_group = adw::PreferencesGroup::new(); + + let profiles = get_profiles(); + for (name, path) in &profiles { + add_profile_row(&prefs_group, name, path); + } + + // Add new profile row + let add_row = adw::ActionRow::new(); + + let name_entry = adw::EntryRow::new(); + // TRANSLATORS: Entry title for new profile name + name_entry.set_title(&gettext("New Profile Name")); + name_entry.set_hexpand(true); + + let add_btn = gtk::Button::with_label(&gettext("Add")); + add_btn.add_css_class("suggested-action"); + add_btn.set_valign(Align::Center); + + let name_entry_clone = name_entry.clone(); + let prefs_group_clone = prefs_group.clone(); + let popup_clone = profiles_popup.clone(); + add_btn.connect_clicked(move |_btn| { + let name = name_entry_clone.text().to_string(); + let trimmed = name.trim(); + if !valid_profile_name(trimmed) { + return; + } + let host_home = get_host_home_dir(); + let safe_name = trimmed.replace(' ', "-"); + let home_path = format!("{host_home}/boxes/{safe_name}"); + set_profile(trimmed, &home_path); + + add_profile_row(&prefs_group_clone, trimmed, &home_path); + name_entry_clone.set_text(""); + popup_clone.queue_draw(); + }); + + add_row.add_prefix(&name_entry); + add_row.add_suffix(&add_btn); + prefs_group.add(&add_row); + + main_box.append(&heading); + main_box.append(&body); + main_box.append(&prefs_group); + + profiles_popup.set_child(Some(&main_box)); + profiles_popup.present(); +} diff --git a/src/utils.rs b/src/utils.rs index 20f3743..9f9c540 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ use adw::StyleManager; use gettextrs::{bind_textdomain_codeset, setlocale, textdomain, LocaleCategory}; use gtk::gio::Settings; -use gtk::prelude::SettingsExt; +use gtk::prelude::{SettingsExt, SettingsExtManual}; use std::collections::HashMap; use std::env; use std::path::Path; @@ -890,6 +890,76 @@ pub fn get_download_dir_path() -> String { }) } +/// Returns the user's home directory, used as a base for profile paths. +pub fn get_host_home_dir() -> String { + env::var("HOME").unwrap_or_else(|_| ".".to_string()) +} + +/// Validates a profile name. +/// Rules: not empty after trimming, at most 32 characters, and only letters, +/// digits, space, `-` and `_`. +pub fn valid_profile_name(name: &str) -> bool { + let trimmed = name.trim(); + if trimmed.is_empty() || trimmed.len() > 32 { + return false; + } + trimmed + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == ' ' || c == '-' || c == '_') +} + +/// Opens a profile's home directory in the system file manager, creating it +/// first if it is not there yet: distrobox only creates it when a box using the +/// profile is built, and an empty folder is more useful to look at than an +/// error. Spawned rather than waited on, so the file manager starting up does +/// not block the interface. +pub fn open_path_in_file_manager(path: &str) { + let _ = run_command("mkdir", Some(&["-p", "--", path])); + + let mut cmd = Command::new("xdg-open"); + if is_flatpak() { + cmd = Command::new("flatpak-spawn"); + cmd.arg("--host"); + cmd.arg("xdg-open"); + } + cmd.arg(path); + let _ = cmd.spawn(); +} + +/// Returns all profiles as a vector of (name, home_path) tuples, sorted by name. +pub fn get_profiles() -> Vec<(String, String)> { + let settings = Settings::new(APP_ID); + let profiles: HashMap = settings.get::>("profiles"); + let mut vec: Vec<(String, String)> = profiles.into_iter().collect(); + vec.sort_by(|a, b| a.0.cmp(&b.0)); + vec +} + +/// Adds or replaces a profile. Empty name or path is a no-op. +pub fn set_profile(name: &str, home_path: &str) { + let name = name.trim(); + let home_path = home_path.trim(); + if name.is_empty() || home_path.is_empty() { + return; + } + let settings = Settings::new(APP_ID); + let mut profiles: HashMap = settings.get::>("profiles"); + profiles.insert(name.to_string(), home_path.to_string()); + let _ = settings.set("profiles", &profiles); +} + +/// Removes a profile by name. +pub fn remove_profile(name: &str) { + let name = name.trim(); + if name.is_empty() { + return; + } + let settings = Settings::new(APP_ID); + let mut profiles: HashMap = settings.get::>("profiles"); + profiles.remove(name); + let _ = settings.set("profiles", &profiles); +} + #[cfg(test)] mod tests { use super::{detect_pkg_manager, PkgManager}; @@ -968,3 +1038,25 @@ mod tests { ); } } + +#[cfg(test)] +mod profile_tests { + use super::valid_profile_name; + + #[test] + fn valid_profile_names_accepted() { + assert!(valid_profile_name("work")); + assert!(valid_profile_name("Personal 2")); + assert!(valid_profile_name("a_b-c")); + } + + #[test] + fn invalid_profile_names_rejected() { + assert!(!valid_profile_name("")); + assert!(!valid_profile_name(" ")); + assert!(!valid_profile_name("a".repeat(40).as_str())); + assert!(!valid_profile_name("a/b")); + assert!(!valid_profile_name("a\"b")); + assert!(!valid_profile_name("a$b")); + } +} From fb4f08401c5faec0cd9e7ed491b0e71589f5547a Mon Sep 17 00:00:00 2001 From: Kacper Paczos Date: Tue, 25 Aug 2026 23:18:13 +0200 Subject: [PATCH 2/2] fix: one row decides a new box's home, not three MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first version of this left the Home Directory entry and its folder button in place beside the new Profile combo, with the combo writing into the entry. Two controls for one setting, and nothing said which of them won - the field even kept its "Leave blank for default" label while the combo was the thing actually deciding. The row is now the only control. Its choices are Host (shared home), the profiles, and Custom folder…, which opens the same folder chooser as before; the chosen directory shows as the row's subtitle, so the path is still visible. Cancelling the chooser falls back to Host rather than leaving the row claiming a folder nobody picked. What is lost is typing a path by hand, which the entry allowed. In a graphical form a folder chooser is the better way to name a directory, and the alternative was keeping an ambiguity in the interface to preserve it. --- src/main.rs | 95 +++++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/src/main.rs b/src/main.rs index 49f16d5..0f0cfb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ use gettextrs::gettext; +use std::cell::RefCell; use std::path::Path; +use std::rc::Rc; use std::thread; use adw::{ @@ -35,8 +37,8 @@ use utils::{ has_file_extension, has_host_access, has_podman_or_docker_installed, open_path_in_file_manager, remove_profile, set_profile, set_up_localisation, valid_profile_name, ADD_ICON_NAMES, APPLICATIONS_ICON_NAMES, ASSEMBLE_FALLBACK_ICON_NAMES, COPY_ICON_NAMES, INFO_ICON_NAMES, - INSTALL_PACKAGE_ICON_NAMES, MENU_ICON_NAMES, OPEN_FILE_ICON_NAMES, REMOVE_ICON_NAMES, - STOP_ICON_NAMES, TERMINAL_ICON_NAMES, TRASH_ICON_NAMES, UPGRADE_ICON_NAMES, WARNING_ICON_NAMES, + INSTALL_PACKAGE_ICON_NAMES, MENU_ICON_NAMES, REMOVE_ICON_NAMES, STOP_ICON_NAMES, + TERMINAL_ICON_NAMES, TRASH_ICON_NAMES, UPGRADE_ICON_NAMES, WARNING_ICON_NAMES, }; const APP_ID: &str = "io.github.dvlv.boxbuddyrs"; @@ -913,50 +915,24 @@ fn create_new_distrobox(window: &ApplicationWindow) { // TRANSLATORS: Entry Label - Name input for new distrobox name_entry_row.set_title(&gettext("Name")); - // Profile selection - let choose_home_btn = - gtk::Button::from_icon_name(&get_available_icon_name(OPEN_FILE_ICON_NAMES)); - choose_home_btn.set_margin_top(10); - choose_home_btn.set_margin_bottom(10); - let home_select_row = adw::ActionRow::new(); - home_select_row.set_activatable_widget(Some(&choose_home_btn)); - home_select_row.add_suffix(&choose_home_btn); - - // home entry row for manual edit / custom path (kept insensitive, driven by combo) - let home_entry_row = adw::EntryRow::new(); - home_entry_row.set_hexpand(true); - home_entry_row.set_sensitive(false); - //Additional Volumes - will not be shown without host access let volume_box_list = gtk::ListBox::new(); volume_box_list.set_selection_mode(gtk::SelectionMode::None); volume_box_list.add_css_class("boxed-list"); volume_box_list.set_visible(false); - // TRANSLATORS: Entry Label - Select home directory for new distrobox - home_entry_row.set_title(&gettext("Home Directory (Leave blank for default)")); - home_entry_row.set_width_request(600); - home_select_row.add_prefix(&home_entry_row); - let home_entry_row_future_clone = home_entry_row.clone(); - - let home_row_for_picker = home_entry_row.clone(); - choose_home_btn.connect_clicked(clone!(@weak window => move |_btn| { - let home_clone = home_row_for_picker.clone(); - let file_dialog = FileDialog::builder().modal(false).build(); - file_dialog.select_folder(Some(&window), None::<&gio::Cancellable>, clone!(@weak window => move |result| { - if let Ok(file) = result { - let home_path = file.path().unwrap().into_os_string().into_string().unwrap(); - home_clone.set_text(&home_path); - } - })); - })); - - // Profile combo row + // One row decides where the box's home is: the host's, a profile's, or a + // folder picked on the spot. A separate path field beside it would only + // raise the question of which of the two wins. let profiles = get_profiles(); + //TRANSLATORS: Profile choice meaning "no separate home, share the host's" let mut profile_names = vec![gettext("Host (shared home)")]; for (name, _path) in &profiles { profile_names.push(name.clone()); } + //TRANSLATORS: Last profile choice - opens a folder chooser for a one-off home + profile_names.push(gettext("Custom folder…")); + let custom_index = (profile_names.len() - 1) as u32; let profile_strlist = gtk::StringList::new( &profile_names .iter() @@ -964,23 +940,42 @@ fn create_new_distrobox(window: &ApplicationWindow) { .collect::>(), ); + // The home path the Create button will use; empty means the host's home. + let chosen_home: Rc> = Rc::new(RefCell::new(String::new())); + let profile_combo = adw::ComboRow::new(); + //TRANSLATORS: Combo Row Title - which home the new box is given profile_combo.set_title(&gettext("Profile")); profile_combo.set_model(Some(&profile_strlist)); profile_combo.set_selected(0); - let profile_combo_clone = profile_combo.clone(); - let home_entry_row_combo_clone = home_entry_row.clone(); + let combo_for_handler = profile_combo.clone(); + let chosen_home_combo = chosen_home.clone(); let profiles_clone = profiles.clone(); - profile_combo.connect_selected_item_notify(move |_combo| { - let selected = profile_combo_clone.selected(); - if selected == 0 { - // "Host (shared home)" - empty path - home_entry_row_combo_clone.set_text(""); + profile_combo.connect_selected_item_notify(clone!(@weak window => move |_combo| { + let selected = combo_for_handler.selected(); + if selected == custom_index { + let combo_for_pick = combo_for_handler.clone(); + let chosen_for_pick = chosen_home_combo.clone(); + let file_dialog = FileDialog::builder().modal(false).build(); + file_dialog.select_folder(Some(&window), None::<&gio::Cancellable>, move |result| { + if let Ok(file) = result { + if let Some(path) = file.path().and_then(|p| p.into_os_string().into_string().ok()) { + combo_for_pick.set_subtitle(&path); + chosen_for_pick.replace(path); + return; + } + } + // Nothing picked: fall back to the host so the row cannot claim + // a folder that was never chosen. + combo_for_pick.set_selected(0); + }); + } else if selected == 0 { + profile_combo_set_home(&combo_for_handler, &chosen_home_combo, ""); } else if let Some((_name, path)) = profiles_clone.get((selected - 1) as usize) { - home_entry_row_combo_clone.set_text(path); + profile_combo_set_home(&combo_for_handler, &chosen_home_combo, path); } - }); + })); // hostname let hostname_entry_row = adw::EntryRow::new(); @@ -1023,7 +1018,7 @@ fn create_new_distrobox(window: &ApplicationWindow) { let loading_spinner = gtk::Spinner::new(); - let home_row = home_entry_row_future_clone.clone(); + let chosen_home_for_create = chosen_home.clone(); let hn_row = hostname_entry_row.clone(); let ne_row = name_entry_row.clone(); let is_row = image_select_row.clone(); @@ -1033,7 +1028,7 @@ fn create_new_distrobox(window: &ApplicationWindow) { let volume_box_list_clone = volume_box_list.clone(); create_btn.connect_clicked(move |btn| { let mut name = ne_row.text().to_string(); - let mut home_path = home_row.text().to_string(); + let mut home_path = chosen_home_for_create.borrow().clone(); let mut hostname = hn_row.text().to_string(); let use_init = in_row.is_active(); let mut image = is_row @@ -1127,7 +1122,6 @@ fn create_new_distrobox(window: &ApplicationWindow) { boxed_list.append(&init_row); boxed_list.append(&profile_combo); - boxed_list.append(&home_select_row); boxed_list.append(&hostname_entry_row); main_box.append(&boxed_list); @@ -2167,6 +2161,13 @@ fn add_profile_row(group: &adw::PreferencesGroup, name: &str, path: &str) { group.add(&row); } +/// Keeps the row's subtitle and the home path it stands for in step: the +/// subtitle is the only place the chosen directory is visible now. +fn profile_combo_set_home(row: &adw::ComboRow, home: &Rc>, path: &str) { + row.set_subtitle(path); + home.replace(path.to_string()); +} + fn show_profiles_popup(window: &ApplicationWindow) { let profiles_popup = gtk::Window::builder() // TRANSLATORS: Popup Window Title