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

@@ -1,4 +1,59 @@
use gtk::prelude::*;
use std::sync::OnceLock;
/// Ensures the shared letter-icon CSS provider is registered on the default
/// display exactly once. The provider defines `.letter-icon-a` through
/// `.letter-icon-z` (and `.letter-icon-other`) with distinct hue-based
/// background/foreground colors so that individual `build_letter_icon` calls
/// never need to create their own CssProvider.
fn ensure_letter_icon_css() {
static REGISTERED: OnceLock<bool> = OnceLock::new();
REGISTERED.get_or_init(|| {
let provider = gtk::CssProvider::new();
provider.load_from_string(&generate_letter_icon_css());
if let Some(display) = gtk::gdk::Display::default() {
gtk::style_context_add_provider_for_display(
&display,
&provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION + 1,
);
}
true
});
}
/// Generate CSS rules for `.letter-icon-a` through `.letter-icon-z` and a
/// `.letter-icon-other` fallback. Each letter gets a unique hue evenly
/// distributed around the color wheel (saturation 55%, lightness 45% for
/// the background, lightness 97% for the foreground text) so that the 26
/// letter icons are visually distinct while remaining legible.
fn generate_letter_icon_css() -> String {
let mut css = String::with_capacity(4096);
for i in 0u32..26 {
let letter = (b'a' + i as u8) as char;
let hue = (i * 360) / 26;
// HSL background: moderate saturation, medium lightness
// HSL foreground: same hue, very light for contrast
css.push_str(&format!(
"label.letter-icon-{letter} {{ \
background: hsl({hue}, 55%, 45%); \
color: hsl({hue}, 100%, 97%); \
border-radius: 50%; \
font-weight: 700; \
}}\n"
));
}
// Fallback for non-alphabetic first characters
css.push_str(
"label.letter-icon-other { \
background: hsl(0, 0%, 50%); \
color: hsl(0, 0%, 97%); \
border-radius: 50%; \
font-weight: 700; \
}\n"
);
css
}
/// Create a status badge pill label with the given text and style class.
/// Style classes: "success", "warning", "error", "info", "neutral"
@@ -70,61 +125,47 @@ pub fn app_icon(icon_path: Option<&str>, app_name: &str, pixel_size: i32) -> gtk
}
/// Build a colored circle with the first letter of the name as a fallback icon.
///
/// The color CSS classes (`.letter-icon-a` .. `.letter-icon-z`) are registered
/// once via a shared CssProvider. This function only needs to pick the right
/// class and set per-widget sizing, avoiding a new provider per icon.
fn build_letter_icon(name: &str, size: i32) -> gtk::Widget {
let letter = name
// Ensure the shared CSS for all 26 letter classes is loaded
ensure_letter_icon_css();
let first_char = name
.chars()
.find(|c| c.is_alphanumeric())
.unwrap_or('?')
.to_uppercase()
.next()
.unwrap_or('?');
let letter_upper = first_char.to_uppercase().next().unwrap_or('?');
// Pick a color based on the name hash for consistency
let color_index = name.bytes().fold(0u32, |acc, b| acc.wrapping_add(b as u32)) % 6;
let bg_color = match color_index {
0 => "@accent_bg_color",
1 => "@success_bg_color",
2 => "@warning_bg_color",
3 => "@error_bg_color",
4 => "@accent_bg_color",
_ => "@success_bg_color",
};
let fg_color = match color_index {
0 => "@accent_fg_color",
1 => "@success_fg_color",
2 => "@warning_fg_color",
3 => "@error_fg_color",
4 => "@accent_fg_color",
_ => "@success_fg_color",
// Determine the CSS class: letter-icon-a .. letter-icon-z, or letter-icon-other
let css_class = if first_char.is_ascii_alphabetic() {
format!("letter-icon-{}", first_char.to_ascii_lowercase())
} else {
"letter-icon-other".to_string()
};
// Use a label styled as a circle with the letter
// Font size scales with the icon (40% of the circle diameter).
let font_size_pt = size * 4 / 10;
let label = gtk::Label::builder()
.label(&letter.to_string())
.use_markup(true)
.halign(gtk::Align::Center)
.valign(gtk::Align::Center)
.width_request(size)
.height_request(size)
.build();
// Apply inline CSS via a provider on the display
let css_provider = gtk::CssProvider::new();
let unique_class = format!("letter-icon-{}", color_index);
let css = format!(
"label.{} {{ background: {}; color: {}; border-radius: 50%; min-width: {}px; min-height: {}px; font-size: {}px; font-weight: 700; }}",
unique_class, bg_color, fg_color, size, size, size * 4 / 10
// Use Pango markup to set the font size without a per-widget CssProvider.
let markup = format!(
"<span size='{}pt'>{}</span>",
font_size_pt,
glib::markup_escape_text(&letter_upper.to_string()),
);
css_provider.load_from_string(&css);
label.set_markup(&markup);
if let Some(display) = gtk::gdk::Display::default() {
gtk::style_context_add_provider_for_display(
&display,
&css_provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION + 1,
);
}
label.add_css_class(&unique_class);
label.add_css_class(&css_class);
label.upcast()
}