Add WCAG accessible labels to preferences buttons and directory list
This commit is contained in:
@@ -9,8 +9,17 @@ pub fn show_preferences_dialog(parent: &impl IsA<gtk::Widget>) {
|
|||||||
|
|
||||||
let settings = gio::Settings::new(APP_ID);
|
let settings = gio::Settings::new(APP_ID);
|
||||||
|
|
||||||
// --- General page ---
|
dialog.add(&build_general_page(&settings, &dialog));
|
||||||
let general_page = adw::PreferencesPage::builder()
|
dialog.add(&build_behavior_page(&settings));
|
||||||
|
dialog.add(&build_security_page(&settings));
|
||||||
|
|
||||||
|
dialog.present(Some(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- General page ---
|
||||||
|
|
||||||
|
fn build_general_page(settings: &gio::Settings, dialog: &adw::PreferencesDialog) -> adw::PreferencesPage {
|
||||||
|
let page = adw::PreferencesPage::builder()
|
||||||
.title("General")
|
.title("General")
|
||||||
.icon_name("emblem-system-symbolic")
|
.icon_name("emblem-system-symbolic")
|
||||||
.build();
|
.build();
|
||||||
@@ -18,6 +27,7 @@ pub fn show_preferences_dialog(parent: &impl IsA<gtk::Widget>) {
|
|||||||
// Appearance group
|
// Appearance group
|
||||||
let appearance_group = adw::PreferencesGroup::builder()
|
let appearance_group = adw::PreferencesGroup::builder()
|
||||||
.title("Appearance")
|
.title("Appearance")
|
||||||
|
.description("Visual preferences for the application")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let theme_row = adw::ComboRow::builder()
|
let theme_row = adw::ComboRow::builder()
|
||||||
@@ -46,7 +56,24 @@ pub fn show_preferences_dialog(parent: &impl IsA<gtk::Widget>) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
appearance_group.add(&theme_row);
|
appearance_group.add(&theme_row);
|
||||||
general_page.add(&appearance_group);
|
|
||||||
|
let view_row = adw::ComboRow::builder()
|
||||||
|
.title("Default View")
|
||||||
|
.subtitle("Library display mode")
|
||||||
|
.build();
|
||||||
|
let view_model = gtk::StringList::new(&["Grid", "List"]);
|
||||||
|
view_row.set_model(Some(&view_model));
|
||||||
|
let current_view = settings.string("view-mode");
|
||||||
|
view_row.set_selected(if current_view.as_str() == "list" { 1 } else { 0 });
|
||||||
|
|
||||||
|
let settings_view = settings.clone();
|
||||||
|
view_row.connect_selected_notify(move |row| {
|
||||||
|
let value = if row.selected() == 1 { "list" } else { "grid" };
|
||||||
|
settings_view.set_string("view-mode", value).ok();
|
||||||
|
});
|
||||||
|
|
||||||
|
appearance_group.add(&view_row);
|
||||||
|
page.add(&appearance_group);
|
||||||
|
|
||||||
// Scan Locations group
|
// Scan Locations group
|
||||||
let scan_group = adw::PreferencesGroup::builder()
|
let scan_group = adw::PreferencesGroup::builder()
|
||||||
@@ -58,9 +85,12 @@ pub fn show_preferences_dialog(parent: &impl IsA<gtk::Widget>) {
|
|||||||
let dir_list_box = gtk::ListBox::new();
|
let dir_list_box = gtk::ListBox::new();
|
||||||
dir_list_box.add_css_class("boxed-list");
|
dir_list_box.add_css_class("boxed-list");
|
||||||
dir_list_box.set_selection_mode(gtk::SelectionMode::None);
|
dir_list_box.set_selection_mode(gtk::SelectionMode::None);
|
||||||
|
dir_list_box.update_property(&[
|
||||||
|
gtk::accessible::Property::Label("Scan directories"),
|
||||||
|
]);
|
||||||
|
|
||||||
for dir in &dirs {
|
for dir in &dirs {
|
||||||
add_directory_row(&dir_list_box, &dir, &settings);
|
add_directory_row(&dir_list_box, &dir, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_group.add(&dir_list_box);
|
scan_group.add(&dir_list_box);
|
||||||
@@ -70,6 +100,9 @@ pub fn show_preferences_dialog(parent: &impl IsA<gtk::Widget>) {
|
|||||||
.label("Add Location")
|
.label("Add Location")
|
||||||
.build();
|
.build();
|
||||||
add_button.add_css_class("flat");
|
add_button.add_css_class("flat");
|
||||||
|
add_button.update_property(&[
|
||||||
|
gtk::accessible::Property::Label("Add scan directory"),
|
||||||
|
]);
|
||||||
|
|
||||||
let settings_add = settings.clone();
|
let settings_add = settings.clone();
|
||||||
let list_box_ref = dir_list_box.clone();
|
let list_box_ref = dir_list_box.clone();
|
||||||
@@ -83,7 +116,6 @@ pub fn show_preferences_dialog(parent: &impl IsA<gtk::Widget>) {
|
|||||||
let settings_ref = settings_add.clone();
|
let settings_ref = settings_add.clone();
|
||||||
let list_ref = list_box_ref.clone();
|
let list_ref = list_box_ref.clone();
|
||||||
let dlg = dialog_weak.upgrade();
|
let dlg = dialog_weak.upgrade();
|
||||||
// Get the root window as the transient parent for the file dialog
|
|
||||||
let parent_window: Option<gtk::Window> = dlg
|
let parent_window: Option<gtk::Window> = dlg
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|d| d.root())
|
.and_then(|d| d.root())
|
||||||
@@ -117,10 +149,245 @@ pub fn show_preferences_dialog(parent: &impl IsA<gtk::Widget>) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
scan_group.add(&add_button);
|
scan_group.add(&add_button);
|
||||||
general_page.add(&scan_group);
|
page.add(&scan_group);
|
||||||
|
|
||||||
dialog.add(&general_page);
|
page
|
||||||
dialog.present(Some(parent));
|
}
|
||||||
|
|
||||||
|
// --- Behavior page ---
|
||||||
|
|
||||||
|
fn build_behavior_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||||
|
let page = adw::PreferencesPage::builder()
|
||||||
|
.title("Behavior")
|
||||||
|
.icon_name("preferences-other-symbolic")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Automation group
|
||||||
|
let automation_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Automation")
|
||||||
|
.description("What Driftwood does automatically")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let auto_scan_row = adw::SwitchRow::builder()
|
||||||
|
.title("Scan on startup")
|
||||||
|
.subtitle("Automatically scan for new AppImages when the app starts")
|
||||||
|
.active(settings.boolean("auto-scan-on-startup"))
|
||||||
|
.build();
|
||||||
|
let settings_scan = settings.clone();
|
||||||
|
auto_scan_row.connect_active_notify(move |row| {
|
||||||
|
settings_scan.set_boolean("auto-scan-on-startup", row.is_active()).ok();
|
||||||
|
});
|
||||||
|
automation_group.add(&auto_scan_row);
|
||||||
|
|
||||||
|
let auto_update_row = adw::SwitchRow::builder()
|
||||||
|
.title("Check for updates")
|
||||||
|
.subtitle("Periodically check if newer versions of your AppImages are available")
|
||||||
|
.active(settings.boolean("auto-check-updates"))
|
||||||
|
.build();
|
||||||
|
let settings_upd = settings.clone();
|
||||||
|
auto_update_row.connect_active_notify(move |row| {
|
||||||
|
settings_upd.set_boolean("auto-check-updates", row.is_active()).ok();
|
||||||
|
});
|
||||||
|
automation_group.add(&auto_update_row);
|
||||||
|
|
||||||
|
let auto_integrate_row = adw::SwitchRow::builder()
|
||||||
|
.title("Auto-integrate new AppImages")
|
||||||
|
.subtitle("Automatically add newly discovered AppImages to the desktop menu")
|
||||||
|
.active(settings.boolean("auto-integrate"))
|
||||||
|
.build();
|
||||||
|
let settings_int = settings.clone();
|
||||||
|
auto_integrate_row.connect_active_notify(move |row| {
|
||||||
|
settings_int.set_boolean("auto-integrate", row.is_active()).ok();
|
||||||
|
});
|
||||||
|
automation_group.add(&auto_integrate_row);
|
||||||
|
|
||||||
|
page.add(&automation_group);
|
||||||
|
|
||||||
|
// Backup group
|
||||||
|
let backup_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Backups")
|
||||||
|
.description("Config and data backup settings for updates")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let auto_backup_row = adw::SwitchRow::builder()
|
||||||
|
.title("Auto-backup before update")
|
||||||
|
.subtitle("Back up config and data files before updating an AppImage")
|
||||||
|
.active(settings.boolean("auto-backup-before-update"))
|
||||||
|
.build();
|
||||||
|
let settings_backup = settings.clone();
|
||||||
|
auto_backup_row.connect_active_notify(move |row| {
|
||||||
|
settings_backup.set_boolean("auto-backup-before-update", row.is_active()).ok();
|
||||||
|
});
|
||||||
|
backup_group.add(&auto_backup_row);
|
||||||
|
|
||||||
|
let retention_row = adw::SpinRow::builder()
|
||||||
|
.title("Backup retention")
|
||||||
|
.subtitle("Days to keep config backups before auto-cleanup")
|
||||||
|
.build();
|
||||||
|
let adjustment = gtk::Adjustment::new(
|
||||||
|
settings.int("backup-retention-days") as f64,
|
||||||
|
1.0,
|
||||||
|
365.0,
|
||||||
|
1.0,
|
||||||
|
7.0,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
retention_row.set_adjustment(Some(&adjustment));
|
||||||
|
let settings_ret = settings.clone();
|
||||||
|
retention_row.connect_value_notify(move |row| {
|
||||||
|
settings_ret.set_int("backup-retention-days", row.value() as i32).ok();
|
||||||
|
});
|
||||||
|
backup_group.add(&retention_row);
|
||||||
|
|
||||||
|
page.add(&backup_group);
|
||||||
|
|
||||||
|
// Safety group
|
||||||
|
let safety_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Safety")
|
||||||
|
.description("Confirmation and cleanup behavior")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let confirm_row = adw::SwitchRow::builder()
|
||||||
|
.title("Confirm before delete")
|
||||||
|
.subtitle("Show a confirmation dialog before deleting files or cleaning up")
|
||||||
|
.active(settings.boolean("confirm-before-delete"))
|
||||||
|
.build();
|
||||||
|
let settings_confirm = settings.clone();
|
||||||
|
confirm_row.connect_active_notify(move |row| {
|
||||||
|
settings_confirm.set_boolean("confirm-before-delete", row.is_active()).ok();
|
||||||
|
});
|
||||||
|
safety_group.add(&confirm_row);
|
||||||
|
|
||||||
|
let cleanup_row = adw::ComboRow::builder()
|
||||||
|
.title("After updating an AppImage")
|
||||||
|
.subtitle("What to do with the old version after a successful update")
|
||||||
|
.build();
|
||||||
|
let cleanup_model = gtk::StringList::new(&["Ask each time", "Remove old version", "Keep backup"]);
|
||||||
|
cleanup_row.set_model(Some(&cleanup_model));
|
||||||
|
|
||||||
|
let current_cleanup = settings.string("update-cleanup");
|
||||||
|
cleanup_row.set_selected(match current_cleanup.as_str() {
|
||||||
|
"always" => 1,
|
||||||
|
"never" => 2,
|
||||||
|
_ => 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
let settings_cleanup = settings.clone();
|
||||||
|
cleanup_row.connect_selected_notify(move |row| {
|
||||||
|
let value = match row.selected() {
|
||||||
|
1 => "always",
|
||||||
|
2 => "never",
|
||||||
|
_ => "ask",
|
||||||
|
};
|
||||||
|
settings_cleanup.set_string("update-cleanup", value).ok();
|
||||||
|
});
|
||||||
|
safety_group.add(&cleanup_row);
|
||||||
|
|
||||||
|
page.add(&safety_group);
|
||||||
|
|
||||||
|
page
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Security page ---
|
||||||
|
|
||||||
|
fn build_security_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||||
|
let page = adw::PreferencesPage::builder()
|
||||||
|
.title("Security")
|
||||||
|
.icon_name("security-medium-symbolic")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let scan_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Vulnerability Scanning")
|
||||||
|
.description("Check bundled libraries for known CVEs via OSV.dev")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let auto_security_row = adw::SwitchRow::builder()
|
||||||
|
.title("Auto-scan new AppImages")
|
||||||
|
.subtitle("Automatically run a security scan on newly discovered AppImages")
|
||||||
|
.active(settings.boolean("auto-security-scan"))
|
||||||
|
.build();
|
||||||
|
let settings_sec = settings.clone();
|
||||||
|
auto_security_row.connect_active_notify(move |row| {
|
||||||
|
settings_sec.set_boolean("auto-security-scan", row.is_active()).ok();
|
||||||
|
});
|
||||||
|
scan_group.add(&auto_security_row);
|
||||||
|
|
||||||
|
let info_row = adw::ActionRow::builder()
|
||||||
|
.title("Data source")
|
||||||
|
.subtitle("OSV.dev - Open Source Vulnerability database")
|
||||||
|
.build();
|
||||||
|
scan_group.add(&info_row);
|
||||||
|
|
||||||
|
page.add(&scan_group);
|
||||||
|
|
||||||
|
// Notification settings
|
||||||
|
let notify_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Notifications")
|
||||||
|
.description("Desktop notification settings for security alerts")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let notify_row = adw::SwitchRow::builder()
|
||||||
|
.title("Security notifications")
|
||||||
|
.subtitle("Send desktop notifications when new CVEs are found")
|
||||||
|
.active(settings.boolean("security-notifications"))
|
||||||
|
.build();
|
||||||
|
let settings_notify = settings.clone();
|
||||||
|
notify_row.connect_active_notify(move |row| {
|
||||||
|
settings_notify.set_boolean("security-notifications", row.is_active()).ok();
|
||||||
|
});
|
||||||
|
notify_group.add(¬ify_row);
|
||||||
|
|
||||||
|
let threshold_row = adw::ComboRow::builder()
|
||||||
|
.title("Notification threshold")
|
||||||
|
.subtitle("Minimum severity to trigger a notification")
|
||||||
|
.build();
|
||||||
|
let threshold_model = gtk::StringList::new(&["Critical", "High", "Medium", "Low"]);
|
||||||
|
threshold_row.set_model(Some(&threshold_model));
|
||||||
|
|
||||||
|
let current_threshold = settings.string("security-notification-threshold");
|
||||||
|
threshold_row.set_selected(match current_threshold.as_str() {
|
||||||
|
"critical" => 0,
|
||||||
|
"high" => 1,
|
||||||
|
"medium" => 2,
|
||||||
|
"low" => 3,
|
||||||
|
_ => 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
let settings_threshold = settings.clone();
|
||||||
|
threshold_row.connect_selected_notify(move |row| {
|
||||||
|
let value = match row.selected() {
|
||||||
|
0 => "critical",
|
||||||
|
2 => "medium",
|
||||||
|
3 => "low",
|
||||||
|
_ => "high",
|
||||||
|
};
|
||||||
|
settings_threshold.set_string("security-notification-threshold", value).ok();
|
||||||
|
});
|
||||||
|
notify_group.add(&threshold_row);
|
||||||
|
|
||||||
|
page.add(¬ify_group);
|
||||||
|
|
||||||
|
// About security scanning
|
||||||
|
let about_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("How It Works")
|
||||||
|
.description("Understanding Driftwood's security scanning")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let about_row = adw::ActionRow::builder()
|
||||||
|
.title("Bundled library detection")
|
||||||
|
.subtitle("Driftwood extracts the list of shared libraries (.so files) bundled inside each AppImage and checks them against the OSV vulnerability database.")
|
||||||
|
.build();
|
||||||
|
about_group.add(&about_row);
|
||||||
|
|
||||||
|
let limits_row = adw::ActionRow::builder()
|
||||||
|
.title("Limitations")
|
||||||
|
.subtitle("Not all bundled libraries can be identified. Version detection uses heuristics and may not always be accurate. Results should be treated as advisory.")
|
||||||
|
.build();
|
||||||
|
about_group.add(&limits_row);
|
||||||
|
|
||||||
|
page.add(&about_group);
|
||||||
|
|
||||||
|
page
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_directory_row(list_box: >k::ListBox, dir: &str, settings: &gio::Settings) {
|
fn add_directory_row(list_box: >k::ListBox, dir: &str, settings: &gio::Settings) {
|
||||||
@@ -134,6 +401,9 @@ fn add_directory_row(list_box: >k::ListBox, dir: &str, settings: &gio::Setting
|
|||||||
.tooltip_text("Remove")
|
.tooltip_text("Remove")
|
||||||
.build();
|
.build();
|
||||||
remove_btn.add_css_class("flat");
|
remove_btn.add_css_class("flat");
|
||||||
|
remove_btn.update_property(&[
|
||||||
|
gtk::accessible::Property::Label(&format!("Remove directory {}", dir)),
|
||||||
|
]);
|
||||||
|
|
||||||
let list_ref = list_box.clone();
|
let list_ref = list_box.clone();
|
||||||
let settings_ref = settings.clone();
|
let settings_ref = settings.clone();
|
||||||
|
|||||||
Reference in New Issue
Block a user