Add Phase 5 enhancements: security, i18n, analysis, backup, notifications
- Database v8 migration: tags, pinned, avg_startup_ms columns - Security scanning with CVE matching and batch scan - Bundled library extraction and vulnerability reports - Desktop notification system for security alerts - Backup/restore system for AppImage configurations - i18n framework with gettext support - Runtime analysis and Wayland compatibility detection - AppStream metadata and Flatpak-style build support - File watcher module for live directory monitoring - Preferences panel with GSettings integration - CLI interface for headless operation - Detail view: tabbed layout with ViewSwitcher in title bar, health score, sandbox controls, changelog links - Library view: sort dropdown, context menu enhancements - Dashboard: system status, disk usage, launch history - Security report page with scan and export - Packaging: meson build, PKGBUILD, metainfo
This commit is contained in:
@@ -2,10 +2,11 @@ use adw::prelude::*;
|
||||
use gtk::gio;
|
||||
|
||||
use crate::config::APP_ID;
|
||||
use crate::i18n::i18n;
|
||||
|
||||
pub fn show_preferences_dialog(parent: &impl IsA<gtk::Widget>) {
|
||||
let dialog = adw::PreferencesDialog::new();
|
||||
dialog.set_title("Preferences");
|
||||
dialog.set_title(&i18n("Preferences"));
|
||||
|
||||
let settings = gio::Settings::new(APP_ID);
|
||||
|
||||
@@ -20,22 +21,22 @@ pub fn show_preferences_dialog(parent: &impl IsA<gtk::Widget>) {
|
||||
|
||||
fn build_general_page(settings: &gio::Settings, dialog: &adw::PreferencesDialog) -> adw::PreferencesPage {
|
||||
let page = adw::PreferencesPage::builder()
|
||||
.title("General")
|
||||
.title(&i18n("General"))
|
||||
.icon_name("emblem-system-symbolic")
|
||||
.build();
|
||||
|
||||
// Appearance group
|
||||
let appearance_group = adw::PreferencesGroup::builder()
|
||||
.title("Appearance")
|
||||
.description("Visual preferences for the application")
|
||||
.title(&i18n("Appearance"))
|
||||
.description(&i18n("Visual preferences for the application"))
|
||||
.build();
|
||||
|
||||
let theme_row = adw::ComboRow::builder()
|
||||
.title("Color Scheme")
|
||||
.subtitle("Choose light, dark, or follow system preference")
|
||||
.title(&i18n("Color Scheme"))
|
||||
.subtitle(&i18n("Choose light, dark, or follow system preference"))
|
||||
.build();
|
||||
|
||||
let model = gtk::StringList::new(&["Follow System", "Light", "Dark"]);
|
||||
let model = gtk::StringList::new(&[&i18n("Follow System"), &i18n("Light"), &i18n("Dark")]);
|
||||
theme_row.set_model(Some(&model));
|
||||
|
||||
let current = settings.string("color-scheme");
|
||||
@@ -58,10 +59,10 @@ fn build_general_page(settings: &gio::Settings, dialog: &adw::PreferencesDialog)
|
||||
appearance_group.add(&theme_row);
|
||||
|
||||
let view_row = adw::ComboRow::builder()
|
||||
.title("Default View")
|
||||
.subtitle("Library display mode")
|
||||
.title(&i18n("Default View"))
|
||||
.subtitle(&i18n("Library display mode"))
|
||||
.build();
|
||||
let view_model = gtk::StringList::new(&["Grid", "List"]);
|
||||
let view_model = gtk::StringList::new(&[&i18n("Grid"), &i18n("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 });
|
||||
@@ -73,12 +74,13 @@ fn build_general_page(settings: &gio::Settings, dialog: &adw::PreferencesDialog)
|
||||
});
|
||||
|
||||
appearance_group.add(&view_row);
|
||||
|
||||
page.add(&appearance_group);
|
||||
|
||||
// Scan Locations group
|
||||
let scan_group = adw::PreferencesGroup::builder()
|
||||
.title("Scan Locations")
|
||||
.description("Directories to scan for AppImage files")
|
||||
.title(&i18n("Scan Locations"))
|
||||
.description(&i18n("Directories to scan for AppImage files"))
|
||||
.build();
|
||||
|
||||
let dirs = settings.strv("scan-directories");
|
||||
@@ -86,7 +88,7 @@ fn build_general_page(settings: &gio::Settings, dialog: &adw::PreferencesDialog)
|
||||
dir_list_box.add_css_class("boxed-list");
|
||||
dir_list_box.set_selection_mode(gtk::SelectionMode::None);
|
||||
dir_list_box.update_property(&[
|
||||
gtk::accessible::Property::Label("Scan directories"),
|
||||
gtk::accessible::Property::Label(&i18n("Scan directories")),
|
||||
]);
|
||||
|
||||
for dir in &dirs {
|
||||
@@ -97,11 +99,11 @@ fn build_general_page(settings: &gio::Settings, dialog: &adw::PreferencesDialog)
|
||||
|
||||
// Add location button
|
||||
let add_button = gtk::Button::builder()
|
||||
.label("Add Location")
|
||||
.label(&i18n("Add Location"))
|
||||
.build();
|
||||
add_button.add_css_class("flat");
|
||||
add_button.update_property(&[
|
||||
gtk::accessible::Property::Label("Add scan directory"),
|
||||
gtk::accessible::Property::Label(&i18n("Add scan directory")),
|
||||
]);
|
||||
|
||||
let settings_add = settings.clone();
|
||||
@@ -109,7 +111,7 @@ fn build_general_page(settings: &gio::Settings, dialog: &adw::PreferencesDialog)
|
||||
let dialog_weak = dialog.downgrade();
|
||||
add_button.connect_clicked(move |_| {
|
||||
let file_dialog = gtk::FileDialog::builder()
|
||||
.title("Choose a directory")
|
||||
.title(i18n("Choose a directory"))
|
||||
.modal(true)
|
||||
.build();
|
||||
|
||||
@@ -158,19 +160,19 @@ fn build_general_page(settings: &gio::Settings, dialog: &adw::PreferencesDialog)
|
||||
|
||||
fn build_behavior_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
let page = adw::PreferencesPage::builder()
|
||||
.title("Behavior")
|
||||
.title(&i18n("Behavior"))
|
||||
.icon_name("preferences-other-symbolic")
|
||||
.build();
|
||||
|
||||
// Automation group
|
||||
let automation_group = adw::PreferencesGroup::builder()
|
||||
.title("Automation")
|
||||
.description("What Driftwood does automatically")
|
||||
.title(&i18n("Automation"))
|
||||
.description(&i18n("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")
|
||||
.title(&i18n("Scan on startup"))
|
||||
.subtitle(&i18n("Automatically scan for new AppImages when the app starts"))
|
||||
.active(settings.boolean("auto-scan-on-startup"))
|
||||
.build();
|
||||
let settings_scan = settings.clone();
|
||||
@@ -180,8 +182,8 @@ fn build_behavior_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
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")
|
||||
.title(&i18n("Check for updates"))
|
||||
.subtitle(&i18n("Periodically check if newer versions of your AppImages are available"))
|
||||
.active(settings.boolean("auto-check-updates"))
|
||||
.build();
|
||||
let settings_upd = settings.clone();
|
||||
@@ -191,8 +193,8 @@ fn build_behavior_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
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")
|
||||
.title(&i18n("Auto-integrate new AppImages"))
|
||||
.subtitle(&i18n("Automatically add newly discovered AppImages to the desktop menu"))
|
||||
.active(settings.boolean("auto-integrate"))
|
||||
.build();
|
||||
let settings_int = settings.clone();
|
||||
@@ -205,13 +207,13 @@ fn build_behavior_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
|
||||
// Backup group
|
||||
let backup_group = adw::PreferencesGroup::builder()
|
||||
.title("Backups")
|
||||
.description("Config and data backup settings for updates")
|
||||
.title(&i18n("Backups"))
|
||||
.description(&i18n("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")
|
||||
.title(&i18n("Auto-backup before update"))
|
||||
.subtitle(&i18n("Back up config and data files before updating an AppImage"))
|
||||
.active(settings.boolean("auto-backup-before-update"))
|
||||
.build();
|
||||
let settings_backup = settings.clone();
|
||||
@@ -221,8 +223,8 @@ fn build_behavior_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
backup_group.add(&auto_backup_row);
|
||||
|
||||
let retention_row = adw::SpinRow::builder()
|
||||
.title("Backup retention")
|
||||
.subtitle("Days to keep config backups before auto-cleanup")
|
||||
.title(&i18n("Backup retention"))
|
||||
.subtitle(&i18n("Days to keep config backups before auto-cleanup"))
|
||||
.build();
|
||||
let adjustment = gtk::Adjustment::new(
|
||||
settings.int("backup-retention-days") as f64,
|
||||
@@ -243,13 +245,13 @@ fn build_behavior_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
|
||||
// Safety group
|
||||
let safety_group = adw::PreferencesGroup::builder()
|
||||
.title("Safety")
|
||||
.description("Confirmation and cleanup behavior")
|
||||
.title(&i18n("Safety"))
|
||||
.description(&i18n("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")
|
||||
.title(&i18n("Confirm before delete"))
|
||||
.subtitle(&i18n("Show a confirmation dialog before deleting files or cleaning up"))
|
||||
.active(settings.boolean("confirm-before-delete"))
|
||||
.build();
|
||||
let settings_confirm = settings.clone();
|
||||
@@ -259,10 +261,10 @@ fn build_behavior_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
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")
|
||||
.title(&i18n("After updating an AppImage"))
|
||||
.subtitle(&i18n("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"]);
|
||||
let cleanup_model = gtk::StringList::new(&[&i18n("Ask each time"), &i18n("Remove old version"), &i18n("Keep backup")]);
|
||||
cleanup_row.set_model(Some(&cleanup_model));
|
||||
|
||||
let current_cleanup = settings.string("update-cleanup");
|
||||
@@ -292,18 +294,18 @@ fn build_behavior_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
|
||||
fn build_security_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
let page = adw::PreferencesPage::builder()
|
||||
.title("Security")
|
||||
.title(&i18n("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")
|
||||
.title(&i18n("Vulnerability Scanning"))
|
||||
.description(&i18n("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")
|
||||
.title(&i18n("Auto-scan new AppImages"))
|
||||
.subtitle(&i18n("Automatically run a security scan on newly discovered AppImages"))
|
||||
.active(settings.boolean("auto-security-scan"))
|
||||
.build();
|
||||
let settings_sec = settings.clone();
|
||||
@@ -313,8 +315,8 @@ fn build_security_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
scan_group.add(&auto_security_row);
|
||||
|
||||
let info_row = adw::ActionRow::builder()
|
||||
.title("Data source")
|
||||
.subtitle("OSV.dev - Open Source Vulnerability database")
|
||||
.title(&i18n("Data source"))
|
||||
.subtitle(&i18n("OSV.dev - Open Source Vulnerability database"))
|
||||
.build();
|
||||
scan_group.add(&info_row);
|
||||
|
||||
@@ -322,13 +324,13 @@ fn build_security_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
|
||||
// Notification settings
|
||||
let notify_group = adw::PreferencesGroup::builder()
|
||||
.title("Notifications")
|
||||
.description("Desktop notification settings for security alerts")
|
||||
.title(&i18n("Notifications"))
|
||||
.description(&i18n("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")
|
||||
.title(&i18n("Security notifications"))
|
||||
.subtitle(&i18n("Send desktop notifications when new CVEs are found"))
|
||||
.active(settings.boolean("security-notifications"))
|
||||
.build();
|
||||
let settings_notify = settings.clone();
|
||||
@@ -338,10 +340,10 @@ fn build_security_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
notify_group.add(¬ify_row);
|
||||
|
||||
let threshold_row = adw::ComboRow::builder()
|
||||
.title("Notification threshold")
|
||||
.subtitle("Minimum severity to trigger a notification")
|
||||
.title(&i18n("Notification threshold"))
|
||||
.subtitle(&i18n("Minimum severity to trigger a notification"))
|
||||
.build();
|
||||
let threshold_model = gtk::StringList::new(&["Critical", "High", "Medium", "Low"]);
|
||||
let threshold_model = gtk::StringList::new(&[&i18n("Critical"), &i18n("High"), &i18n("Medium"), &i18n("Low")]);
|
||||
threshold_row.set_model(Some(&threshold_model));
|
||||
|
||||
let current_threshold = settings.string("security-notification-threshold");
|
||||
@@ -369,19 +371,19 @@ fn build_security_page(settings: &gio::Settings) -> adw::PreferencesPage {
|
||||
|
||||
// About security scanning
|
||||
let about_group = adw::PreferencesGroup::builder()
|
||||
.title("How It Works")
|
||||
.description("Understanding Driftwood's security scanning")
|
||||
.title(&i18n("How It Works"))
|
||||
.description(&i18n("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.")
|
||||
.title(&i18n("Bundled library detection"))
|
||||
.subtitle(&i18n("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.")
|
||||
.title(&i18n("Limitations"))
|
||||
.subtitle(&i18n("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);
|
||||
|
||||
@@ -398,11 +400,11 @@ fn add_directory_row(list_box: >k::ListBox, dir: &str, settings: &gio::Setting
|
||||
let remove_btn = gtk::Button::builder()
|
||||
.icon_name("edit-delete-symbolic")
|
||||
.valign(gtk::Align::Center)
|
||||
.tooltip_text("Remove")
|
||||
.tooltip_text(&i18n("Remove"))
|
||||
.build();
|
||||
remove_btn.add_css_class("flat");
|
||||
remove_btn.update_property(&[
|
||||
gtk::accessible::Property::Label(&format!("Remove directory {}", dir)),
|
||||
gtk::accessible::Property::Label(&format!("{} {}", i18n("Remove directory"), dir)),
|
||||
]);
|
||||
|
||||
let list_ref = list_box.clone();
|
||||
|
||||
Reference in New Issue
Block a user