use adw::prelude::*; use gtk::gio; use crate::core::fuse; use crate::i18n::i18n; /// Show a FUSE installation wizard dialog. pub fn show_fuse_wizard(parent: &impl IsA) { let system_info = fuse::detect_system_fuse(); if system_info.status.is_functional() { let dialog = adw::AlertDialog::builder() .heading(&i18n("FUSE is working")) .body(&i18n("libfuse2 is installed and functional. No action needed.")) .build(); dialog.add_response("ok", &i18n("OK")); dialog.present(Some(parent)); return; } let install_cmd = match fuse::get_fuse_install_command() { Some(cmd) => cmd, None => { let dialog = adw::AlertDialog::builder() .heading(&i18n("Unknown distribution")) .body(&i18n( "Could not detect your Linux distribution. Please install libfuse2 manually using your package manager.", )) .build(); dialog.add_response("ok", &i18n("OK")); dialog.present(Some(parent)); return; } }; let dialog = adw::Dialog::builder() .title(&i18n("Install FUSE")) .content_width(480) .content_height(350) .build(); let toolbar = adw::ToolbarView::new(); let header = adw::HeaderBar::new(); toolbar.add_top_bar(&header); let content = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .spacing(18) .margin_start(24) .margin_end(24) .margin_top(18) .margin_bottom(18) .build(); let title = gtk::Label::builder() .label(&i18n("Additional setup needed")) .xalign(0.0) .build(); title.add_css_class("title-3"); content.append(&title); let explanation = gtk::Label::builder() .label(&i18n( "Most apps need a small system component called FUSE to start quickly. \ Without it, apps will still work but will take longer to start each time. \ You can install it now (requires your password) or skip and use the slower method.", )) .wrap(true) .xalign(0.0) .build(); content.append(&explanation); // Show the detected issue let issue_label = gtk::Label::builder() .label(&format!("Status: {}", system_info.status.label())) .xalign(0.0) .build(); issue_label.add_css_class("heading"); content.append(&issue_label); // Show the command let cmd_label = gtk::Label::builder() .label(&format!("Command: {}", install_cmd)) .xalign(0.0) .selectable(true) .build(); cmd_label.add_css_class("monospace"); content.append(&cmd_label); // Status label for result let status_label = gtk::Label::builder() .xalign(0.0) .wrap(true) .visible(false) .build(); content.append(&status_label); // Install button let button_box = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .spacing(12) .halign(gtk::Align::End) .margin_top(12) .build(); let skip_btn = gtk::Button::builder() .label(&i18n("Skip and use slower method")) .tooltip_text(&i18n("Apps will still work, but they will take longer to start because they unpack themselves each time")) .build(); skip_btn.add_css_class("flat"); skip_btn.add_css_class("pill"); let install_btn = gtk::Button::builder() .label(&i18n("Install via pkexec")) .build(); install_btn.add_css_class("suggested-action"); install_btn.add_css_class("pill"); button_box.append(&skip_btn); button_box.append(&install_btn); content.append(&button_box); toolbar.set_content(Some(&content)); dialog.set_child(Some(&toolbar)); // Skip button just closes the dialog let dialog_weak = dialog.downgrade(); skip_btn.connect_clicked(move |_| { if let Some(dlg) = dialog_weak.upgrade() { dlg.close(); } }); let cmd = install_cmd.clone(); let status_ref = status_label.clone(); let btn_ref = install_btn.clone(); install_btn.connect_clicked(move |_| { btn_ref.set_sensitive(false); status_ref.set_visible(true); status_ref.set_label(&i18n("Running installation...")); let cmd = cmd.clone(); let status = status_ref.clone(); let btn = btn_ref.clone(); glib::spawn_future_local(async move { let result = gio::spawn_blocking(move || { // Split command into program and args let parts: Vec<&str> = cmd.split_whitespace().collect(); if parts.is_empty() { return Err("Empty command".to_string()); } let program = parts[0]; let args = &parts[1..]; std::process::Command::new("pkexec") .arg(program) .args(args) .status() .map_err(|e| format!("Failed to run pkexec: {}", e)) }) .await; match result { Ok(Ok(exit)) if exit.success() => { // Verify FUSE is now working let fuse_info = fuse::detect_system_fuse(); if fuse_info.status.is_functional() { status.set_label(&i18n("FUSE installed successfully! AppImages should now mount natively.")); status.add_css_class("success"); } else { status.set_label(&i18n("Installation completed but FUSE still not detected. A reboot may be required.")); status.add_css_class("warning"); } } Ok(Ok(_)) => { status.set_label(&i18n("Installation was cancelled or failed.")); status.add_css_class("error"); btn.set_sensitive(true); } Ok(Err(e)) => { status.set_label(&format!("Error: {}", e)); status.add_css_class("error"); btn.set_sensitive(true); } Err(_) => { status.set_label(&i18n("Task failed unexpectedly.")); status.add_css_class("error"); btn.set_sensitive(true); } } }); }); dialog.present(Some(parent)); }