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:
lashman
2026-02-27 17:16:41 +02:00
parent a7ed3742fb
commit 423323d5a9
51 changed files with 10583 additions and 481 deletions

View File

@@ -96,9 +96,28 @@ impl LibraryView {
.title("Driftwood")
.build();
// Add button (shows drop overlay)
let add_button_icon = gtk::Image::from_icon_name("list-add-symbolic");
let add_button_label = gtk::Label::new(Some(&i18n("Add app")));
let add_button_content = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(6)
.build();
add_button_content.append(&add_button_icon);
add_button_content.append(&add_button_label);
let add_button = gtk::Button::builder()
.child(&add_button_content)
.tooltip_text(&i18n("Add AppImage"))
.build();
add_button.add_css_class("flat");
add_button.set_action_name(Some("win.show-drop-hint"));
add_button.update_property(&[AccessibleProperty::Label("Add AppImage")]);
let header_bar = adw::HeaderBar::builder()
.title_widget(&title_widget)
.build();
header_bar.pack_start(&add_button);
header_bar.pack_end(&menu_button);
header_bar.pack_end(&search_button);
header_bar.pack_end(&view_toggle_box);
@@ -175,8 +194,8 @@ impl LibraryView {
.description(&i18n(
"Driftwood manages your AppImage collection - scanning for apps, \
integrating them into your desktop, and keeping them up to date.\n\n\
Add AppImages to ~/Applications or ~/Downloads, or configure \
custom scan locations in Preferences.",
Drag AppImage files here, or add them to ~/Applications or ~/Downloads, \
then use Scan Now to find them.",
))
.child(&empty_button_box)
.build();
@@ -196,13 +215,13 @@ impl LibraryView {
.selection_mode(gtk::SelectionMode::None)
.homogeneous(true)
.min_children_per_line(2)
.max_children_per_line(4)
.row_spacing(14)
.column_spacing(14)
.margin_top(14)
.margin_bottom(14)
.margin_start(14)
.margin_end(14)
.max_children_per_line(5)
.row_spacing(12)
.column_spacing(12)
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();
flow_box.update_property(&[AccessibleProperty::Label("AppImage library grid")]);
@@ -463,9 +482,14 @@ impl LibraryView {
let name = record.app_name.as_deref().unwrap_or(&record.filename);
// Structured two-line subtitle:
// Line 1: Description snippet or file path
// Line 1: Description snippet or file path (or "Analyzing..." if pending)
// Line 2: Version + size
let line1 = if let Some(ref desc) = record.description {
let is_analyzing = record.app_name.is_none()
&& record.analysis_status.as_deref() != Some("complete");
let line1 = if is_analyzing {
i18n("Analyzing...")
} else if let Some(ref desc) = record.description {
if !desc.is_empty() {
let snippet: String = desc.chars().take(60).collect();
if snippet.len() < desc.len() {
@@ -496,7 +520,7 @@ impl LibraryView {
.activatable(true)
.build();
// Icon prefix (48x48 with rounded clipping and letter fallback)
// Icon prefix with rounded clipping and letter fallback
let icon = widgets::app_icon(
record.icon_path.as_deref(),
name,
@@ -583,19 +607,19 @@ fn build_context_menu(record: &AppImageRecord) -> gtk::gio::Menu {
// Section 2: Actions
let section2 = gtk::gio::Menu::new();
section2.append(Some("Check for Updates"), Some(&format!("win.check-update(int64 {})", record.id)));
section2.append(Some("Scan for Vulnerabilities"), Some(&format!("win.scan-security(int64 {})", record.id)));
section2.append(Some("Security check"), Some(&format!("win.scan-security(int64 {})", record.id)));
menu.append_section(None, &section2);
// Section 3: Integration + folder
let section3 = gtk::gio::Menu::new();
let integrate_label = if record.integrated { "Remove Integration" } else { "Integrate" };
let integrate_label = if record.integrated { "Remove from app menu" } else { "Add to app menu" };
section3.append(Some(integrate_label), Some(&format!("win.toggle-integration(int64 {})", record.id)));
section3.append(Some("Open Containing Folder"), Some(&format!("win.open-folder(int64 {})", record.id)));
section3.append(Some("Show in file manager"), Some(&format!("win.open-folder(int64 {})", record.id)));
menu.append_section(None, &section3);
// Section 4: Clipboard
let section4 = gtk::gio::Menu::new();
section4.append(Some("Copy Path"), Some(&format!("win.copy-path(int64 {})", record.id)));
section4.append(Some("Copy file location"), Some(&format!("win.copy-path(int64 {})", record.id)));
menu.append_section(None, &section4);
menu