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

@@ -5,6 +5,7 @@ use crate::core::database::{AppImageRecord, Database};
use crate::core::fuse::FuseStatus;
use crate::core::integrator;
use crate::core::wayland::WaylandStatus;
use crate::i18n::{i18n, i18n_f};
use super::widgets;
/// Show a confirmation dialog before integrating an AppImage into the desktop.
@@ -18,14 +19,14 @@ pub fn show_integration_dialog(
let name = record.app_name.as_deref().unwrap_or(&record.filename);
let dialog = adw::AlertDialog::builder()
.heading(&format!("Integrate {}?", name))
.body("This will add the application to your desktop menu.")
.heading(&i18n_f("Integrate {name}?", &[("{name}", name)]))
.body(&i18n("This will add the application to your desktop menu."))
.close_response("cancel")
.default_response("integrate")
.build();
dialog.add_response("cancel", "Cancel");
dialog.add_response("integrate", "Integrate");
dialog.add_response("cancel", &i18n("Cancel"));
dialog.add_response("integrate", &i18n("Integrate"));
dialog.set_response_appearance("integrate", adw::ResponseAppearance::Suggested);
// Build extra content with details
@@ -40,12 +41,12 @@ pub fn show_integration_dialog(
identity_box.add_css_class("boxed-list");
identity_box.set_selection_mode(gtk::SelectionMode::None);
identity_box.update_property(&[
gtk::accessible::Property::Label("Application details"),
gtk::accessible::Property::Label(&i18n("Application details")),
]);
// Name
let name_row = adw::ActionRow::builder()
.title("Application")
.title(&i18n("Application"))
.subtitle(name)
.build();
if let Some(ref icon_path) = record.icon_path {
@@ -65,7 +66,7 @@ pub fn show_integration_dialog(
// Version
if let Some(ref version) = record.app_version {
let row = adw::ActionRow::builder()
.title("Version")
.title(&i18n("Version"))
.subtitle(version)
.build();
identity_box.append(&row);
@@ -78,12 +79,12 @@ pub fn show_integration_dialog(
actions_box.add_css_class("boxed-list");
actions_box.set_selection_mode(gtk::SelectionMode::None);
actions_box.update_property(&[
gtk::accessible::Property::Label("Integration actions"),
gtk::accessible::Property::Label(&i18n("Integration actions")),
]);
let desktop_row = adw::ActionRow::builder()
.title("Desktop entry")
.subtitle("A .desktop file will be created in ~/.local/share/applications")
.title(&i18n("Desktop entry"))
.subtitle(&i18n("A .desktop file will be created in ~/.local/share/applications"))
.build();
let check1 = gtk::Image::from_icon_name("emblem-ok-symbolic");
check1.set_valign(gtk::Align::Center);
@@ -91,8 +92,8 @@ pub fn show_integration_dialog(
actions_box.append(&desktop_row);
let icon_row = adw::ActionRow::builder()
.title("Icon")
.subtitle("The app icon will be installed to ~/.local/share/icons")
.title(&i18n("Icon"))
.subtitle(&i18n("The app icon will be installed to ~/.local/share/icons"))
.build();
let check2 = gtk::Image::from_icon_name("emblem-ok-symbolic");
check2.set_valign(gtk::Align::Center);
@@ -114,23 +115,31 @@ pub fn show_integration_dialog(
.map(FuseStatus::from_str)
.unwrap_or(FuseStatus::MissingLibfuse2);
let mut warnings: Vec<(&str, &str, &str)> = Vec::new();
let mut warnings: Vec<(String, String, String)> = Vec::new();
if wayland_status == WaylandStatus::X11Only {
warnings.push(("X11 only", "This app does not support Wayland and will run through XWayland", "X11"));
warnings.push((
i18n("X11 only"),
i18n("This app does not support Wayland and will run through XWayland"),
"X11".to_string(),
));
} else if wayland_status == WaylandStatus::XWayland {
warnings.push(("XWayland", "This app runs through the XWayland compatibility layer", "XWayland"));
warnings.push((
i18n("XWayland"),
i18n("This app runs through the XWayland compatibility layer"),
"XWayland".to_string(),
));
}
if !fuse_status.is_functional() {
let fuse_msg = match fuse_status {
FuseStatus::Fuse3Only => "Only FUSE3 is installed - libfuse2 may be needed",
FuseStatus::NoFusermount => "fusermount not found - AppImage mount may fail",
FuseStatus::NoDevFuse => "/dev/fuse not available - AppImage mount will fail",
FuseStatus::MissingLibfuse2 => "libfuse2 not installed - fallback extraction will be used",
_ => "FUSE issue detected",
FuseStatus::Fuse3Only => i18n("Only FUSE3 is installed - libfuse2 may be needed"),
FuseStatus::NoFusermount => i18n("fusermount not found - AppImage mount may fail"),
FuseStatus::NoDevFuse => i18n("/dev/fuse not available - AppImage mount will fail"),
FuseStatus::MissingLibfuse2 => i18n("libfuse2 not installed - fallback extraction will be used"),
_ => i18n("FUSE issue detected"),
};
warnings.push(("FUSE", fuse_msg, fuse_status.label()));
warnings.push(("FUSE".to_string(), fuse_msg, fuse_status.label().to_string()));
}
if !warnings.is_empty() {
@@ -149,7 +158,7 @@ pub fn show_integration_dialog(
warning_icon.set_pixel_size(16);
warning_header.append(&warning_icon);
let warning_title = gtk::Label::builder()
.label("Compatibility Notes")
.label(&i18n("Compatibility Notes"))
.css_classes(["title-4"])
.halign(gtk::Align::Start)
.build();
@@ -162,8 +171,8 @@ pub fn show_integration_dialog(
for (title, subtitle, badge_text) in &warnings {
let row = adw::ActionRow::builder()
.title(*title)
.subtitle(*subtitle)
.title(title.as_str())
.subtitle(subtitle.as_str())
.build();
let badge = widgets::status_badge(badge_text, "warning");
badge.set_valign(gtk::Align::Center);