feat: Flatpak-Paket (Vorbereitung für Flathub)

- packaging/flatpak/eu.pansi.ink-notes.yml: Runtime freedesktop 26.08,
  Rust aus rust-stable, Abhängigkeiten offline aus cargo-sources.json.
  Rechte: Wayland/X11, GPU, Lesen von Bildern/Downloads/Schreibtisch,
  Schreiben in den Dokumente-Ordner (kein Zugriff auf den ganzen
  Home-Ordner).
- Im Flatpak sucht und installiert ink-notes keine Updates selbst
  (Vorgabe von Flathub); der Update-Dialog verweist auf die
  Software-Verwaltung.
- Export-Ordner aus user-dirs.dirs (z. B. ~/Dokumente) statt fest
  ~/Documents; nur dieser Ordner ist im Flatpak beschreibbar.
- Metainfo und Desktop-Datei auf Englisch mit deutscher Übersetzung,
  Release-Einträge, unterstützte Eingabegeräte.
- Test: cargo-sources.json deckt Cargo.lock ab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 16:18:37 +02:00
co-authored by Claude Opus 5.5
parent dd702524ca
commit e399c357d8
12 changed files with 5709 additions and 39 deletions
+13 -2
View File
@@ -46,7 +46,9 @@ pub fn sanitize_filename_component(title: &str) -> String {
}
}
/// Ermittelt das Standard-Dokumentenverzeichnis ($XDG_DOCUMENTS_DIR mit Fallback ~/Documents).
/// Ermittelt das Standard-Dokumentenverzeichnis: `$XDG_DOCUMENTS_DIR`, sonst der Eintrag in
/// `~/.config/user-dirs.dirs` (z. B. „Dokumente“), sonst `~/Documents`. Im Flatpak ist nur
/// dieser Ordner beschreibbar, der Fallback allein träfe bei deutschen Systemen daneben.
pub fn default_documents_dir() -> PathBuf {
if let Ok(dir) = std::env::var("XDG_DOCUMENTS_DIR") {
if !dir.trim().is_empty() {
@@ -56,7 +58,16 @@ pub fn default_documents_dir() -> PathBuf {
if let Ok(home) = std::env::var("HOME") {
if !home.trim().is_empty() {
return PathBuf::from(home).join("Documents");
let home = PathBuf::from(home);
let user_dirs =
std::fs::read_to_string(home.join(".config/user-dirs.dirs")).unwrap_or_default();
if let Some(dir) =
crate::file_picker::parse_user_dir(&user_dirs, "XDG_DOCUMENTS_DIR", &home)
.filter(|dir| *dir != home)
{
return dir;
}
return home.join("Documents");
}
}
+2
View File
@@ -66,9 +66,11 @@ pub const EN: &[(&str, &str)] = &[
("Die beschädigte Originaldatei wurde in Quarantäne gesichert:", "The damaged original file was saved to quarantine:"),
("Die Datei ist kein lesbares PDF", "The file is not a readable PDF"),
("Die einzige Seite eines Notizbuchs kann nicht gelöscht werden", "The only page of a notebook cannot be deleted"),
("Die Flatpak-Version wird über die Software-Verwaltung aktualisiert.", "The Flatpak version is updated through the software manager."),
("Die heruntergeladene Datei existiert nicht mehr.", "The downloaded file no longer exists."),
("die oberste Ebene", "the top level"),
("Die Zwischenablage ist leer", "The clipboard is empty"),
("Diese Version wird über die Software-Verwaltung aktualisiert (z. B. GNOME Software oder „flatpak update“).", "This version is updated through the software manager (e.g. GNOME Software or “flatpak update”)."),
("Dieser Ordner ist leer.", "This folder is empty."),
("Dokument '{title}' erstellt", "Document '{title}' created"),
("Dokument '{title}' geöffnet", "Document '{title}' opened"),
+14 -1
View File
@@ -4281,6 +4281,18 @@ pub fn render_ui(
ui.label(egui::RichText::new(format!("({})", runtime_name)).color(egui::Color32::from_gray(150)));
});
if !updater.runtime().self_update_allowed() {
ui.add_space(8.0);
ui.label(
egui::RichText::new(t!(
"Diese Version wird über die Software-Verwaltung aktualisiert \
(z. B. GNOME Software oder „flatpak update“)."
))
.color(egui::Color32::from_gray(210)),
);
return;
}
ui.horizontal(|ui| {
ui.label(egui::RichText::new(t!("Server:")).color(egui::Color32::from_gray(180)));
ui.label(egui::RichText::new(format!("{}/{}/{}", updater.config().server_url, updater.config().repo_owner, updater.config().repo_name)).color(egui::Color32::from_gray(210)));
@@ -4446,7 +4458,8 @@ pub fn render_ui(
let action_label = match updater.runtime() {
crate::updater::RuntimeEnvironment::AppImage { .. } => t!("🚀 AppImage aktualisieren & ausführen"),
crate::updater::RuntimeEnvironment::DebPackage { .. } => t!("📦 Paket mit Software-Center öffnen / installieren"),
crate::updater::RuntimeEnvironment::PortableOrDev { .. } => t!("📂 Im Dateimanager anzeigen"),
crate::updater::RuntimeEnvironment::PortableOrDev { .. }
| crate::updater::RuntimeEnvironment::Flatpak => t!("📂 Im Dateimanager anzeigen"),
};
ui.horizontal(|ui| {
+41
View File
@@ -118,11 +118,20 @@ pub enum RuntimeEnvironment {
DebPackage { binary_path: PathBuf },
/// Aus Quellcode / Entwicklungs-Binary (z.B. target/debug oder target/release)
PortableOrDev { binary_path: PathBuf },
/// Als Flatpak installiert: Updates kommen über die Software-Verwaltung, ink-notes lädt
/// selbst nichts herunter (Vorgabe von Flathub)
Flatpak,
}
impl RuntimeEnvironment {
/// Erkennt die aktuelle Laufzeitumgebung anhand der Umgebung und des Binärpfads.
pub fn detect() -> Self {
if is_flatpak(
std::env::var("FLATPAK_ID").ok().as_deref(),
Path::new("/.flatpak-info").exists(),
) {
return RuntimeEnvironment::Flatpak;
}
let appimage_var = std::env::var("APPIMAGE").ok();
let current_exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("ink-notes"));
Self::detect_from(appimage_var.as_deref(), &current_exe)
@@ -156,8 +165,20 @@ impl RuntimeEnvironment {
RuntimeEnvironment::AppImage { .. } => t!("AppImage (Portabel)"),
RuntimeEnvironment::DebPackage { .. } => t!("System-Paket (.deb)"),
RuntimeEnvironment::PortableOrDev { .. } => t!("Entwicklung / Lokal"),
RuntimeEnvironment::Flatpak => "Flatpak",
}
}
/// Ob ink-notes selbst nach Updates suchen und sie installieren darf
pub fn self_update_allowed(&self) -> bool {
!matches!(self, RuntimeEnvironment::Flatpak)
}
}
/// Läuft ink-notes in einer Flatpak-Sandbox (`FLATPAK_ID` gesetzt oder `/.flatpak-info`
/// vorhanden)?
pub fn is_flatpak(flatpak_id: Option<&str>, info_file_exists: bool) -> bool {
flatpak_id.is_some_and(|id| !id.trim().is_empty()) || info_file_exists
}
/// Bereinigt und validiert einen Asset-Dateinamen gegen Path Traversal Angriffe.
@@ -578,6 +599,9 @@ impl UpdaterController {
/// Fragt im Hintergrund die Gitea-API ab.
pub fn check_for_updates(&mut self) {
if !self.runtime.self_update_allowed() {
return;
}
if matches!(
self.status,
UpdaterStatus::Checking | UpdaterStatus::Downloading { .. }
@@ -887,6 +911,10 @@ impl UpdaterController {
}
Ok(())
}
RuntimeEnvironment::Flatpak => Err(t!(
"Die Flatpak-Version wird über die Software-Verwaltung aktualisiert."
)
.to_string()),
}
}
@@ -1074,6 +1102,19 @@ dcdeb7ac87d9e6cb8022e5a71bc7f2c56d779b4fce4efdd5bae5be3b0214884a ink-notes_0.1.
);
}
#[test]
fn test_flatpak_detection_disables_self_update() {
assert!(is_flatpak(Some("eu.pansi.ink-notes"), false));
assert!(is_flatpak(None, true));
assert!(!is_flatpak(Some(" "), false));
assert!(!is_flatpak(None, false));
assert!(!RuntimeEnvironment::Flatpak.self_update_allowed());
assert!(
RuntimeEnvironment::detect_from(None, Path::new("/usr/bin/ink-notes"))
.self_update_allowed()
);
}
#[test]
fn test_https_enforcement() {
let config = UpdaterConfig {