use adw::prelude::*; use std::rc::Rc; use crate::core::database::Database; use crate::core::launcher; use crate::i18n::i18n; /// Show a first-run permission summary before launching an AppImage. /// Returns a dialog that the caller should present. The `on_proceed` callback /// is called if the user chooses to continue. pub fn show_permission_dialog( parent: &impl IsA, app_name: &str, record_id: i64, db: &Rc, on_proceed: impl Fn() + 'static, ) { let dialog = adw::AlertDialog::builder() .heading(&format!("Launch {}?", app_name)) .body(&i18n( "This AppImage will run with your full user permissions. \ It can access your files, network, and desktop.", )) .build(); let extra = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .spacing(8) .margin_top(12) .build(); let access_label = gtk::Label::builder() .label(&i18n("This app will have access to:")) .xalign(0.0) .build(); access_label.add_css_class("heading"); extra.append(&access_label); let items = [ "Your files and folders", "Internet and network access", "Your screen and windows", "Background system services", ]; for item in &items { let label = gtk::Label::builder() .label(&format!(" - {}", i18n(item))) .xalign(0.0) .build(); extra.append(&label); } // Explanation paragraph let explain = gtk::Label::builder() .label(&i18n( "AppImages run like regular programs on your computer. Unlike phone apps, \ desktop apps typically have full access to your files and system. This is normal.", )) .wrap(true) .xalign(0.0) .margin_top(8) .build(); explain.add_css_class("caption"); explain.add_css_class("dim-label"); extra.append(&explain); // Show firejail option if available if launcher::has_firejail() { let sandbox_note = gtk::Label::builder() .label(&i18n( "You can restrict this app's access later in Details > Security Restrictions.", )) .wrap(true) .xalign(0.0) .margin_top(4) .build(); sandbox_note.add_css_class("dim-label"); extra.append(&sandbox_note); } dialog.set_extra_child(Some(&extra)); dialog.add_response("cancel", &i18n("Cancel")); dialog.add_response("proceed", &i18n("Launch")); dialog.set_response_appearance("proceed", adw::ResponseAppearance::Suggested); dialog.set_default_response(Some("proceed")); dialog.set_close_response("cancel"); let db_ref = db.clone(); dialog.connect_response(Some("proceed"), move |_dlg, _response| { // Mark as prompted so we don't show again db_ref.set_first_run_prompted(record_id, true).ok(); on_proceed(); }); dialog.present(Some(parent)); }